Remove Command::Done and add verification of Command state transitions
BUG:23906692
Change-Id: I1934ad70e1918cc5b40ac07ee1e1ccfee406867e
Reviewed-on: https://weave-review.googlesource.com/1259
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/libweave/examples/ubuntu/main.cc b/libweave/examples/ubuntu/main.cc
index 4c4d251..ff0d450 100644
--- a/libweave/examples/ubuntu/main.cc
+++ b/libweave/examples/ubuntu/main.cc
@@ -119,10 +119,7 @@
result.SetString("_greeting", "Hello " + name);
cmd->SetResults(result, nullptr);
LOG(INFO) << cmd->GetName() << " command finished: " << result;
-
LOG(INFO) << "New state: " << *device_->GetState();
-
- cmd->Done();
}
void OnGreetCommand(const std::weak_ptr<weave::Command>& command) {
@@ -157,12 +154,13 @@
if (cmd_value != cur_state) {
UpdateLedState();
}
- return cmd->Done();
+ cmd->SetResults({}, nullptr);
+ return;
}
weave::ErrorPtr error;
weave::Error::AddTo(&error, FROM_HERE, "example", "invalid_parameter_value",
"Invalid parameters");
- cmd->Abort(error.get());
+ cmd->Abort(error.get(), nullptr);
}
void OnFlasherToggleCommand(const std::weak_ptr<weave::Command>& command) {
@@ -177,12 +175,13 @@
led_status_[led_index] = ~led_status_[led_index];
UpdateLedState();
- return cmd->Done();
+ cmd->SetResults({}, nullptr);
+ return;
}
weave::ErrorPtr error;
weave::Error::AddTo(&error, FROM_HERE, "example", "invalid_parameter_value",
"Invalid parameters");
- cmd->Abort(error.get());
+ cmd->Abort(error.get(), nullptr);
}
void OnUnhandledCommand(const std::weak_ptr<weave::Command>& command) {
diff --git a/libweave/include/weave/command.h b/libweave/include/weave/command.h
index b8bbbd7..c09da32 100644
--- a/libweave/include/weave/command.h
+++ b/libweave/include/weave/command.h
@@ -48,6 +48,9 @@
// Returns the command results.
virtual std::unique_ptr<base::DictionaryValue> GetResults() const = 0;
+ // Returns the command error.
+ virtual const Error* GetError() const = 0;
+
// Updates the command progress. The |progress| should match the schema.
// Returns false if |progress| value is incorrect.
virtual bool SetProgress(const base::DictionaryValue& progress,
@@ -55,17 +58,26 @@
// Updates the command results. The |results| should match the schema.
// Returns false if |results| value is incorrect.
+ // Sets command into terminal "done" state.
+ // TODO(vitalybuka): Rename to Complete.
virtual bool SetResults(const base::DictionaryValue& results,
ErrorPtr* error) = 0;
+ // Sets command into paused state.
+ // This is not terminal state. Command can be resumed with |SetProgress| call.
+ virtual bool Pause(ErrorPtr* error) = 0;
+
+ // Sets command into error state and assign error.
+ // This is not terminal state. Command can be resumed with |SetProgress| call.
+ virtual bool SetError(const Error* command_error, ErrorPtr* error) = 0;
+
// Aborts command execution.
- virtual void Abort(const Error* error) = 0;
+ // Sets command into terminal "aborted" state.
+ virtual bool Abort(const Error* command_error, ErrorPtr* error) = 0;
// Cancels command execution.
- virtual void Cancel() = 0;
-
- // Marks the command as completed successfully.
- virtual void Done() = 0;
+ // Sets command into terminal "canceled" state.
+ virtual bool Cancel(ErrorPtr* error) = 0;
protected:
virtual ~Command() = default;
diff --git a/libweave/include/weave/test/mock_command.h b/libweave/include/weave/test/mock_command.h
index 07fe4b3..c4324df 100644
--- a/libweave/include/weave/test/mock_command.h
+++ b/libweave/include/weave/test/mock_command.h
@@ -28,11 +28,13 @@
MOCK_CONST_METHOD0(MockGetParameters, const std::string&());
MOCK_CONST_METHOD0(MockGetProgress, const std::string&());
MOCK_CONST_METHOD0(MockGetResults, const std::string&());
+ MOCK_CONST_METHOD0(GetError, const Error*());
MOCK_METHOD2(SetProgress, bool(const base::DictionaryValue&, ErrorPtr*));
MOCK_METHOD2(SetResults, bool(const base::DictionaryValue&, ErrorPtr*));
- MOCK_METHOD1(Abort, void(const Error*));
- MOCK_METHOD0(Cancel, void());
- MOCK_METHOD0(Done, void());
+ MOCK_METHOD1(Pause, bool(ErrorPtr*));
+ MOCK_METHOD2(SetError, bool(const Error*, ErrorPtr*));
+ MOCK_METHOD2(Abort, bool(const Error*, ErrorPtr*));
+ MOCK_METHOD1(Cancel, bool(ErrorPtr*));
std::unique_ptr<base::DictionaryValue> GetParameters() const override;
std::unique_ptr<base::DictionaryValue> GetProgress() const override;
diff --git a/libweave/src/base_api_handler.cc b/libweave/src/base_api_handler.cc
index 9e457e8..273162a 100644
--- a/libweave/src/base_api_handler.cc
+++ b/libweave/src/base_api_handler.cc
@@ -74,13 +74,14 @@
errors::commands::kInvalidPropValue,
"Invalid localAnonymousAccessMaxRole value '%s'",
anonymous_access_role.c_str());
- return command->Abort(error.get());
+ command->Abort(error.get(), nullptr);
+ return;
}
device_info_->UpdateBaseConfig(auth_scope, discovery_enabled,
pairing_enabled);
- command->Done();
+ command->SetResults({}, nullptr);
}
void BaseApiHandler::OnConfigChanged(const Settings& settings) {
@@ -112,7 +113,7 @@
parameters->GetString("location", &location);
device_info_->UpdateDeviceInfo(name, description, location);
- command->Done();
+ command->SetResults({}, nullptr);
}
} // namespace weave
diff --git a/libweave/src/commands/cloud_command_proxy_unittest.cc b/libweave/src/commands/cloud_command_proxy_unittest.cc
index e1d96b0..dbff905 100644
--- a/libweave/src/commands/cloud_command_proxy_unittest.cc
+++ b/libweave/src/commands/cloud_command_proxy_unittest.cc
@@ -147,14 +147,14 @@
TEST_F(CloudCommandProxyTest, ImmediateUpdate) {
const char expected[] = "{'state':'done'}";
EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _, _));
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
}
TEST_F(CloudCommandProxyTest, DelayedUpdate) {
// Simulate that the current device state has changed.
current_state_update_id_ = 20;
// No command update is expected here.
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
// Still no command update here...
callbacks_.Notify(19);
// Now we should get the update...
@@ -247,7 +247,7 @@
EXPECT_TRUE(command_instance_->SetProgress(
*CreateDictionaryValue("{'status': 'busy'}"), nullptr));
current_state_update_id_ = 22;
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
// Device state #20 updated.
base::Closure on_success;
@@ -286,7 +286,7 @@
EXPECT_TRUE(command_instance_->SetProgress(
*CreateDictionaryValue("{'status': 'busy'}"), nullptr));
current_state_update_id_ = 22;
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
// Device state 20-21 updated.
base::Closure on_success;
@@ -315,7 +315,7 @@
EXPECT_TRUE(command_instance_->SetProgress(
*CreateDictionaryValue("{'status': 'busy'}"), nullptr));
current_state_update_id_ = 22;
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
// Device state 30 updated.
const char expected[] = R"({
@@ -336,7 +336,7 @@
*CreateDictionaryValue("{'status': 'finished'}"), nullptr));
EXPECT_TRUE(command_instance_->SetResults(
*CreateDictionaryValue("{'sum': 30}"), nullptr));
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
const char expected[] = R"({
'progress': {'status':'finished'},
@@ -360,7 +360,7 @@
// As soon as we change the command, the update to the server should be sent.
const char expected[] = "{'state':'done'}";
EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _, _));
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
}
TEST_F(CloudCommandProxyTest, NonEmptyStateChangeQueue) {
@@ -372,7 +372,7 @@
CreateCommandInstance();
// No command updates right now.
- command_instance_->Done();
+ command_instance_->SetResults({}, nullptr);
// Only when the state #20 is published we should update the command
const char expected[] = "{'state':'done'}";
diff --git a/libweave/src/commands/command_instance.cc b/libweave/src/commands/command_instance.cc
index 45c376d..4701bb6 100644
--- a/libweave/src/commands/command_instance.cc
+++ b/libweave/src/commands/command_instance.cc
@@ -38,10 +38,21 @@
{CommandOrigin::kCloud, "cloud"},
};
-void ReportDestroyedError(ErrorPtr* error) {
+bool ReportDestroyedError(ErrorPtr* error) {
Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
errors::commands::kCommandDestroyed,
"Command has been destroyed");
+ return false;
+}
+
+bool ReportInvalidStateTransition(ErrorPtr* error,
+ CommandStatus from,
+ CommandStatus to) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidState,
+ "State switch impossible: '%s' -> '%s'",
+ EnumToString(from).c_str(), EnumToString(to).c_str());
+ return false;
}
} // namespace
@@ -97,12 +108,14 @@
return TypedValueToJson(results_);
}
+const Error* CommandInstance::GetError() const {
+ return error_.get();
+}
+
bool CommandInstance::SetProgress(const base::DictionaryValue& progress,
ErrorPtr* error) {
- if (!command_definition_) {
- ReportDestroyedError(error);
- return false;
- }
+ if (!command_definition_)
+ return ReportDestroyedError(error);
ObjectPropType obj_prop_type;
obj_prop_type.SetObjectSchema(command_definition_->GetProgress()->Clone());
@@ -111,20 +124,21 @@
return false;
// Change status even if progress unchanged, e.g. 0% -> 0%.
- SetStatus(CommandStatus::kInProgress);
+ if (!SetStatus(CommandStatus::kInProgress, error))
+ return false;
+
if (obj != progress_) {
progress_ = obj;
FOR_EACH_OBSERVER(Observer, observers_, OnProgressChanged());
}
+
return true;
}
bool CommandInstance::SetResults(const base::DictionaryValue& results,
ErrorPtr* error) {
- if (!command_definition_) {
- ReportDestroyedError(error);
- return false;
- }
+ if (!command_definition_)
+ return ReportDestroyedError(error);
ObjectPropType obj_prop_type;
obj_prop_type.SetObjectSchema(command_definition_->GetResults()->Clone());
@@ -136,7 +150,16 @@
results_ = obj;
FOR_EACH_OBSERVER(Observer, observers_, OnResultsChanged());
}
- return true;
+ // Change status even if result is unchanged.
+ bool result = SetStatus(CommandStatus::kDone, error);
+ RemoveFromQueue();
+ // The command will be destroyed after that, so do not access any members.
+ return result;
+}
+
+bool CommandInstance::SetError(const Error* command_error, ErrorPtr* error) {
+ error_ = command_error ? command_error->Clone() : nullptr;
+ return SetStatus(CommandStatus::kError, error);
}
namespace {
@@ -267,31 +290,45 @@
observers_.RemoveObserver(observer);
}
-void CommandInstance::Abort(const Error* error) {
- if (error)
- error_ = error->Clone();
- SetStatus(CommandStatus::kAborted);
- RemoveFromQueue();
- // The command will be destroyed after that, so do not access any members.
+bool CommandInstance::Pause(ErrorPtr* error) {
+ return SetStatus(CommandStatus::kPaused, error);
}
-void CommandInstance::Cancel() {
- SetStatus(CommandStatus::kCancelled);
+bool CommandInstance::Abort(const Error* command_error, ErrorPtr* error) {
+ error_ = command_error ? command_error->Clone() : nullptr;
+ bool result = SetStatus(CommandStatus::kAborted, error);
RemoveFromQueue();
// The command will be destroyed after that, so do not access any members.
+ return result;
}
-void CommandInstance::Done() {
- SetStatus(CommandStatus::kDone);
+bool CommandInstance::Cancel(ErrorPtr* error) {
+ bool result = SetStatus(CommandStatus::kCancelled, error);
RemoveFromQueue();
// The command will be destroyed after that, so do not access any members.
+ return result;
}
-void CommandInstance::SetStatus(CommandStatus status) {
- if (status != status_) {
- status_ = status;
- FOR_EACH_OBSERVER(Observer, observers_, OnStatusChanged());
+bool CommandInstance::SetStatus(CommandStatus status, ErrorPtr* error) {
+ if (status == status_)
+ return true;
+ if (status == CommandStatus::kQueued)
+ return ReportInvalidStateTransition(error, status_, status);
+ switch (status_) {
+ case CommandStatus::kDone:
+ case CommandStatus::kCancelled:
+ case CommandStatus::kAborted:
+ case CommandStatus::kExpired:
+ return ReportInvalidStateTransition(error, status_, status);
+ case CommandStatus::kQueued:
+ case CommandStatus::kInProgress:
+ case CommandStatus::kPaused:
+ case CommandStatus::kError:
+ break;
}
+ status_ = status;
+ FOR_EACH_OBSERVER(Observer, observers_, OnStatusChanged());
+ return true;
}
void CommandInstance::RemoveFromQueue() {
diff --git a/libweave/src/commands/command_instance.h b/libweave/src/commands/command_instance.h
index 0bc3c0f..e8a838d 100644
--- a/libweave/src/commands/command_instance.h
+++ b/libweave/src/commands/command_instance.h
@@ -59,13 +59,15 @@
std::unique_ptr<base::DictionaryValue> GetParameters() const override;
std::unique_ptr<base::DictionaryValue> GetProgress() const override;
std::unique_ptr<base::DictionaryValue> GetResults() const override;
+ const Error* GetError() const override;
bool SetProgress(const base::DictionaryValue& progress,
ErrorPtr* error) override;
bool SetResults(const base::DictionaryValue& results,
ErrorPtr* error) override;
- void Abort(const Error* error) override;
- void Cancel() override;
- void Done() override;
+ bool Pause(ErrorPtr* error) override;
+ bool SetError(const Error* command_error, ErrorPtr* error) override;
+ bool Abort(const Error* command_error, ErrorPtr* error) override;
+ bool Cancel(ErrorPtr* error) override;
// Returns command definition.
const CommandDefinition* GetCommandDefinition() const {
@@ -107,7 +109,7 @@
private:
// Helper function to update the command status.
// Used by Abort(), Cancel(), Done() methods.
- void SetStatus(CommandStatus status);
+ bool SetStatus(CommandStatus status, ErrorPtr* error);
// Helper method that removes this command from the command queue.
// Note that since the command queue owns the lifetime of the command instance
// object, removing a command from the queue will also destroy it.
diff --git a/libweave/src/commands/command_instance_unittest.cc b/libweave/src/commands/command_instance_unittest.cc
index f783ca7..7c2536b 100644
--- a/libweave/src/commands/command_instance_unittest.cc
+++ b/libweave/src/commands/command_instance_unittest.cc
@@ -212,15 +212,37 @@
EXPECT_TRUE(instance->SetResults(*CreateDictionaryValue("{'testResult': 17}"),
nullptr));
- ErrorPtr error;
- Error::AddTo(&error, FROM_HERE, "DOMAIN", "CODE", "MESSAGE");
- instance->Abort(error.get());
-
json->MergeDictionary(CreateDictionaryValue(R"({
'id': 'testId',
'progress': {'progress': 15},
+ 'state': 'done',
+ 'results': {'testResult': 17}
+ })").get());
+
+ auto converted = instance->ToJson();
+ EXPECT_PRED2([](const base::Value& val1,
+ const base::Value& val2) { return val1.Equals(&val2); },
+ *json, *converted);
+}
+
+TEST_F(CommandInstanceTest, ToJsonError) {
+ auto json = CreateDictionaryValue(R"({
+ 'name': 'base.reboot',
+ 'parameters': {}
+ })");
+ auto instance = CommandInstance::FromJson(json.get(), CommandOrigin::kCloud,
+ dict_, nullptr, nullptr);
+ instance->SetID("testId");
+
+ ErrorPtr error;
+ Error::AddTo(&error, FROM_HERE, "DOMAIN", "CODE", "MESSAGE");
+ instance->Abort(error.get(), nullptr);
+
+ json->MergeDictionary(CreateDictionaryValue(R"({
+ 'id': 'testId',
'state': 'aborted',
- 'results': {'testResult': 17},
+ 'progress': {},
+ 'results': {},
'error': {'code': 'CODE', 'message': 'MESSAGE'}
})").get());
diff --git a/libweave/src/commands/schema_constants.cc b/libweave/src/commands/schema_constants.cc
index f53423f..0b0ba40 100644
--- a/libweave/src/commands/schema_constants.cc
+++ b/libweave/src/commands/schema_constants.cc
@@ -26,6 +26,7 @@
const char kInvalidCommandVisibility[] = "invalid_command_visibility";
const char kInvalidMinimalRole[] = "invalid_minimal_role";
const char kCommandDestroyed[] = "command_destroyed";
+const char kInvalidState[] = "invalid_state";
} // namespace commands
} // namespace errors
diff --git a/libweave/src/commands/schema_constants.h b/libweave/src/commands/schema_constants.h
index 6dded05..6cd3061 100644
--- a/libweave/src/commands/schema_constants.h
+++ b/libweave/src/commands/schema_constants.h
@@ -29,6 +29,7 @@
extern const char kInvalidCommandVisibility[];
extern const char kInvalidMinimalRole[];
extern const char kCommandDestroyed[];
+extern const char kInvalidState[];
} // namespace commands
} // namespace errors
diff --git a/libweave/src/device_registration_info_unittest.cc b/libweave/src/device_registration_info_unittest.cc
index f5c53fa..71c9832 100644
--- a/libweave/src/device_registration_info_unittest.cc
+++ b/libweave/src/device_registration_info_unittest.cc
@@ -502,54 +502,51 @@
EXPECT_EQ(GcdState::kConnecting, GetGcdState());
}
-TEST_F(DeviceRegistrationInfoTest, UpdateCommand) {
- ReloadSettings();
- SetAccessToken();
+class DeviceRegistrationInfoUpdateCommandTest
+ : public DeviceRegistrationInfoTest {
+ protected:
+ void SetUp() override {
+ DeviceRegistrationInfoTest::SetUp();
- auto json_cmds = CreateDictionaryValue(R"({
- 'robot': {
- '_jump': {
- 'parameters': {'_height': 'integer'},
- 'progress': {'progress': 'integer'},
- 'results': {'status': 'string'},
- 'minimalRole': 'user'
+ ReloadSettings();
+ SetAccessToken();
+
+ auto json_cmds = CreateDictionaryValue(R"({
+ 'robot': {
+ '_jump': {
+ 'parameters': {'_height': 'integer'},
+ 'progress': {'progress': 'integer'},
+ 'results': {'status': 'string'},
+ 'minimalRole': 'user'
+ }
}
- }
- })");
- EXPECT_TRUE(command_manager_->LoadCommands(*json_cmds, nullptr));
+ })");
+ EXPECT_TRUE(command_manager_->LoadCommands(*json_cmds, nullptr));
- const std::string command_url = dev_reg_->GetServiceURL("commands/1234");
+ command_url_ = dev_reg_->GetServiceURL("commands/1234");
- auto commands_json = CreateValue(R"([{
- 'name':'robot._jump',
- 'id':'1234',
- 'parameters': {'_height': 100},
- 'minimalRole': 'user'
- }])");
- ASSERT_NE(nullptr, commands_json.get());
- const base::ListValue* command_list = nullptr;
- ASSERT_TRUE(commands_json->GetAsList(&command_list));
- PublishCommands(*command_list);
- auto command = command_manager_->FindCommand("1234");
- ASSERT_NE(nullptr, command);
+ auto commands_json = CreateValue(R"([{
+ 'name':'robot._jump',
+ 'id':'1234',
+ 'parameters': {'_height': 100},
+ 'minimalRole': 'user'
+ }])");
+ ASSERT_NE(nullptr, commands_json.get());
+ const base::ListValue* command_list = nullptr;
+ ASSERT_TRUE(commands_json->GetAsList(&command_list));
+ PublishCommands(*command_list);
+ command_ = command_manager_->FindCommand("1234");
+ ASSERT_NE(nullptr, command_);
+ }
+ Command* command_{nullptr};
+ std::string command_url_;
+};
+
+TEST_F(DeviceRegistrationInfoUpdateCommandTest, SetProgress) {
EXPECT_CALL(http_client_,
MockSendRequest(
- http::kPatch, command_url,
- HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
- .WillOnce(WithArgs<3>(Invoke([](const std::string& data) {
- EXPECT_JSON_EQ(R"({"results":{"status":"Ok"}})",
- *CreateDictionaryValue(data));
- base::DictionaryValue json;
- return ReplyWithJson(200, json);
- })));
- EXPECT_TRUE(
- command->SetResults(*CreateDictionaryValue("{'status': 'Ok'}"), nullptr));
- Mock::VerifyAndClearExpectations(&http_client_);
-
- EXPECT_CALL(http_client_,
- MockSendRequest(
- http::kPatch, command_url,
+ http::kPatch, command_url_,
HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
.WillOnce(WithArgs<3>(Invoke([](const std::string& data) {
EXPECT_JSON_EQ(R"({"state":"inProgress"})",
@@ -563,13 +560,36 @@
base::DictionaryValue json;
return ReplyWithJson(200, json);
})));
- EXPECT_TRUE(
- command->SetProgress(*CreateDictionaryValue("{'progress':18}"), nullptr));
+ EXPECT_TRUE(command_->SetProgress(*CreateDictionaryValue("{'progress':18}"),
+ nullptr));
Mock::VerifyAndClearExpectations(&http_client_);
+}
+TEST_F(DeviceRegistrationInfoUpdateCommandTest, SetResults) {
EXPECT_CALL(http_client_,
MockSendRequest(
- http::kPatch, command_url,
+ http::kPatch, command_url_,
+ HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
+ .WillOnce(WithArgs<3>(Invoke([](const std::string& data) {
+ EXPECT_JSON_EQ(R"({"results":{"status":"Ok"}})",
+ *CreateDictionaryValue(data));
+ base::DictionaryValue json;
+ return ReplyWithJson(200, json);
+ })))
+ .WillOnce(WithArgs<3>(Invoke([](const std::string& data) {
+ EXPECT_JSON_EQ(R"({"state":"done"})", *CreateDictionaryValue(data));
+ base::DictionaryValue json;
+ return ReplyWithJson(200, json);
+ })));
+ EXPECT_TRUE(command_->SetResults(*CreateDictionaryValue("{'status': 'Ok'}"),
+ nullptr));
+ Mock::VerifyAndClearExpectations(&http_client_);
+}
+
+TEST_F(DeviceRegistrationInfoUpdateCommandTest, Cancel) {
+ EXPECT_CALL(http_client_,
+ MockSendRequest(
+ http::kPatch, command_url_,
HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
.WillOnce(WithArgs<3>(Invoke([](const std::string& data) {
EXPECT_JSON_EQ(R"({"state":"cancelled"})",
@@ -577,7 +597,7 @@
base::DictionaryValue json;
return ReplyWithJson(200, json);
})));
- command->Cancel();
+ EXPECT_TRUE(command_->Cancel(nullptr));
Mock::VerifyAndClearExpectations(&http_client_);
}
diff --git a/libweave/src/privet/cloud_delegate.cc b/libweave/src/privet/cloud_delegate.cc
index f7a803e..045a235 100644
--- a/libweave/src/privet/cloud_delegate.cc
+++ b/libweave/src/privet/cloud_delegate.cc
@@ -196,10 +196,8 @@
CHECK(user_info.scope() != AuthScope::kNone);
ErrorPtr error;
auto command = GetCommandInternal(id, user_info, &error);
- if (!command)
+ if (!command || !command->Cancel(&error))
return error_callback.Run(error.get());
-
- command->Cancel();
success_callback.Run(*command->ToJson());
}