// Copyright 2023 Northern.tech AS
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

#include <csignal>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>

#include <gtest/gtest.h>
#include <gmock/gmock.h>

#include <api/client.hpp>
#include <common/common.hpp>
#include <client_shared/conf.hpp>
#include <common/device_tier.hpp>
#include <common/error.hpp>
#include <common/events.hpp>
#include <common/key_value_database.hpp>
#include <common/path.hpp>
#include <common/processes.hpp>
#include <common/testing.hpp>

#include <mender-update/context.hpp>
#include <mender-update/inventory.hpp>
#include <mender-update/daemon/context.hpp>
#include <mender-update/daemon/state_machine.hpp>

#define DEPLOYMENT_ID "w81s4fae-7dec-11d0-a765-00a0c91e6bf6"

namespace mender {
namespace update {
namespace daemon {

namespace fs = std::filesystem;

namespace api = mender::api;
namespace common = mender::common;
namespace conf = mender::client_shared::conf;
namespace device_tier = mender::common::device_tier;
namespace error = mender::common::error;
namespace events = mender::common::events;
namespace kvdb = mender::common::key_value_database;
namespace path = mender::common::path;
namespace processes = mender::common::processes;

namespace mtesting = mender::common::testing;

namespace context = mender::update::context;
namespace inventory = mender::update::inventory;

using namespace std;

enum class InstallOutcome {
	SuccessfulInstall,
	SuccessfulRollback,
	UnsuccessfulInstall,
};

enum class ProvideFileSizes {
	Empty,
	Yes,
	No,
};

static std::string ProvideFileSizesString[] = {"", "Yes", "No"};

std::string ProvideFileSizesToString(ProvideFileSizes sizes) {
	return ProvideFileSizesString[static_cast<int>(sizes)];
}

class MockPoster : virtual public mender::common::state_machine::EventPoster<StateEvent> {
public:
	MOCK_METHOD(void, PostEvent, (StateEvent event), (override));
};

struct StateTransitionsTestCase {
	string case_name;
	vector<string> state_chain;
	vector<string> status_log;
	InstallOutcome install_outcome;
	int fail_status_report_count;
	deployments::DeploymentStatus fail_status_report_status;
	bool fail_status_aborted;
	bool long_retry_times;

	vector<string> error_states;
	bool error_forever;
	vector<string> spont_reboot_states;
	bool spont_reboot_forever;
	vector<string> hang_states;
	bool rollback_disabled;
	bool reboot_disabled;
	ProvideFileSizes provide_file_sizes;
	bool broken_download;
	int do_schema_update_at_invocation {-1};
	int use_non_writable_db_after_n_writes {-1};
	bool empty_payload_artifact;
	bool device_type_mismatch {false};
	bool generate_idle_sync_scripts {false};
	// Set it to the string that the database should contain.
	string update_control_string;
	int stop_after_n_deployments {1};
};


vector<StateTransitionsTestCase> idle_and_sync_test_cases {
	StateTransitionsTestCase {
		.case_name = "Normal_flow_Idle_Sync_Idle",
		.state_chain =
			{
				"Idle_Enter_00",
				"Idle_Leave_00",
				"Sync_Enter_00",
				"Sync_Leave_00",
				"Idle_Enter_00",
				"Idle_Leave_00",
				"Sync_Enter_00",
				"Sync_Leave_00",
				"Download_Enter_00",
				"ProvidePayloadFileSizes",
				"Download",
				"Download_Leave_00",
				"ArtifactInstall_Enter_00",
				"ArtifactInstall",
				"ArtifactInstall_Leave_00",
				"ArtifactReboot_Enter_00",
				"ArtifactReboot",
				"ArtifactVerifyReboot",
				"ArtifactReboot_Leave_00",
				"ArtifactCommit_Enter_00",
				"ArtifactCommit",
				"ArtifactCommit_Leave_00",
				"Cleanup", // Leave me, or clang-format is not nice
			},
		.status_log =
			{
				"downloading",
				"installing",
				"rebooting",
				"installing",
				"success",
			},
		.install_outcome = InstallOutcome::SuccessfulInstall,
		.generate_idle_sync_scripts = true,
	},

	StateTransitionsTestCase {
		.case_name = "Normal_flow_DownloadWithFileSizesYes",
		.state_chain =
			{
				"Download_Enter_00",
				"ProvidePayloadFileSizes",
				"DownloadWithFileSizes",
				"Download_Leave_00",
				"ArtifactInstall_Enter_00",
				"ArtifactInstall",
				"ArtifactInstall_Leave_00",
				"ArtifactReboot_Enter_00",
				"ArtifactReboot",
				"ArtifactVerifyReboot",
				"ArtifactReboot_Leave_00",
				"ArtifactCommit_Enter_00",
				"ArtifactCommit",
				"ArtifactCommit_Leave_00",
				"Cleanup", // Leave me, or clang-format is not nice
			},
		.status_log =
			{
				"downloading",
				"installing",
				"rebooting",
				"installing",
				"success",
			},
		.install_outcome = InstallOutcome::SuccessfulInstall,
		.provide_file_sizes = ProvideFileSizes::Yes,
	},

	StateTransitionsTestCase {
		.case_name = "Normal_flow_DownloadWithFileSizesNo",
		.state_chain =
			{
				"Download_Enter_00",
				"ProvidePayloadFileSizes",
				"Download",
				"Download_Leave_00",
				"ArtifactInstall_Enter_00",
				"ArtifactInstall",
				"ArtifactInstall_Leave_00",
				"ArtifactReboot_Enter_00",
				"ArtifactReboot",
				"ArtifactVerifyReboot",
				"ArtifactReboot_Leave_00",
				"ArtifactCommit_Enter_00",
				"ArtifactCommit",
				"ArtifactCommit_Leave_00",
				"Cleanup", // Leave me, or clang-format is not nice
			},
		.status_log =
			{
				"downloading",
				"installing",
				"rebooting",
				"installing",
				"success",
			},
		.install_outcome = InstallOutcome::SuccessfulInstall,
		.provide_file_sizes = ProvideFileSizes::No,
	},

	StateTransitionsTestCase {
		.case_name = "Normal_flow_DownloadWithFileSizesEmpty",
		.state_chain =
			{
				"Download_Enter_00",
				"ProvidePayloadFileSizes",
				"Download",
				"Download_Leave_00",
				"ArtifactInstall_Enter_00",
				"ArtifactInstall",
				"ArtifactInstall_Leave_00",
				"ArtifactReboot_Enter_00",
				"ArtifactReboot",
				"ArtifactVerifyReboot",
				"ArtifactReboot_Leave_00",
				"ArtifactCommit_Enter_00",
				"ArtifactCommit",
				"ArtifactCommit_Leave_00",
				"Cleanup", // Leave me, or clang-format is not nice
			},
		.status_log =
			{
				"downloading",
				"installing",
				"rebooting",
				"installing",
				"success",
			},
		.install_outcome = InstallOutcome::SuccessfulInstall,
		.provide_file_sizes = ProvideFileSizes::Empty,
	},

	StateTransitionsTestCase {
		.case_name = "Idle_Leave_Error",
		.state_chain =
			{
				"Idle_Enter_00",
				"Idle_Leave_00",
				"Sync_Enter_00",
				"Sync_Leave_00",
				"Idle_Enter_00",
				"Idle_Leave_00",
				"Sync_Enter_00",
				"Sync_Leave_00",
				"Download_Enter_00",
				"ProvidePayloadFileSizes",
				"Download",
				"Download_Leave_00",
				"ArtifactInstall_Enter_00",
				"ArtifactInstall",
				"ArtifactInstall_Leave_00",
				"ArtifactReboot_Enter_00",
				"ArtifactReboot",
				"ArtifactVerifyReboot",
				"ArtifactReboot_Leave_00",
				"ArtifactCommit_Enter_00",
				"ArtifactCommit",
				"ArtifactCommit_Leave_00",
				"Cleanup", // Leave me, or clang-format is not nice
			},
		.status_log =
			{
				"downloading",
				"installing",
				"rebooting",
				"installing",
				"success",
			},
		.install_outcome = InstallOutcome::SuccessfulInstall,
		.error_states = {"Idle_Leave_00"},
		.generate_idle_sync_scripts = true,
	},

	StateTransitionsTestCase {
		.case_name = "Sync_Enter_Error",
		.state_chain =
			{
				"Idle_Enter_00",
				"Idle_Leave_00",
				"Sync_Enter_00",
				"Sync_Error_00",
				"Idle_Enter_00",
				"Idle_Leave_00",
				"Sync_Enter_00",
				"Sync_Leave_00",
				"Download_Enter_00",
				"ProvidePayloadFileSizes",
				"Download",
				"Download_Leave_00",
				"ArtifactInstall_Enter_00",
				"ArtifactInstall",
				"ArtifactInstall_Leave_00",
				"ArtifactReboot_Enter_00",
				"ArtifactReboot",
				"ArtifactVerifyReboot",
				"ArtifactReboot_Leave_00",
				"ArtifactCommit_Enter_00",
				"ArtifactCommit",
				"ArtifactCommit_Leave_00",
				"Cleanup", // Leave me, or clang-format is not nice
			},
		.status_log =
			{
				"downloading",
				"installing",
				"rebooting",
				"installing",
				"success",
			},
		.install_outcome = InstallOutcome::SuccessfulInstall,
		.error_states = {"Sync_Enter_00"},
		.generate_idle_sync_scripts = true,
	},

	StateTransitionsTestCase {
		.case_name = "Sync_Leave_Error",
		.state_chain =
			{
				"Idle_Enter_00",
				"Idle_Leave_00",
				"Sync_Enter_00",
				"Sync_Leave_00", // <- Only fails the first time, here
				"Sync_Error_00",
				"Idle_Enter_00",
				"Idle_Leave_00",
				"Sync_Enter_00",
				"Sync_Leave_00",
				"Download_Enter_00",
				"ProvidePayloadFileSizes",
				"Download",
				"Download_Leave_00",
				"ArtifactInstall_Enter_00",
				"ArtifactInstall",
				"ArtifactInstall_Leave_00",
				"ArtifactReboot_Enter_00",
				"ArtifactReboot",
				"ArtifactVerifyReboot",
				"ArtifactReboot_Leave_00",
				"ArtifactCommit_Enter_00",
				"ArtifactCommit",
				"ArtifactCommit_Leave_00",
				"Cleanup", // Leave me, or clang-format is not nice
			},
		.status_log =
			{
				"downloading",
				"installing",
				"rebooting",
				"installing",
				"success",
			},
		.install_outcome = InstallOutcome::SuccessfulInstall,
		.error_states = {"Sync_Leave_00"},
		.generate_idle_sync_scripts = true,
	},

	StateTransitionsTestCase {
		.case_name = "Sync_Leave_Error_multiple_deployments",
		.state_chain =
			{
				"Idle_Enter_00",
				"Idle_Leave_00",
				"Sync_Enter_00", // <- Only fails the first time, during inventory
				// Start of inventory.
				"Sync_Error_00",
				// End of inventory.
				"Idle_Enter_00",
				"Idle_Leave_00",
				"Sync_Enter_00",
				// Start of deployment.
				"Sync_Leave_00", // <- Only fails the second time, during deployment
				"Sync_Error_00",
				// End of deployment.
				"Idle_Enter_00",
				"Idle_Leave_00",
				// Start of inventory.
				"Sync_Enter_00",
				"Sync_Leave_00",
				// End of inventory.
				"Idle_Enter_00",
				"Idle_Leave_00",
				// Start of deployment.
				"Sync_Enter_00",
				"Sync_Leave_00",
				"Download_Enter_00",
				"ProvidePayloadFileSizes",
				"Download",
				"Download_Leave_00",
				"ArtifactInstall_Enter_00",
				"ArtifactInstall",
				"ArtifactInstall_Leave_00",
				"ArtifactReboot_Enter_00",
				"ArtifactReboot",
				"ArtifactVerifyReboot",
				"ArtifactReboot_Leave_00",
				"ArtifactCommit_Enter_00",
				"ArtifactCommit",
				"ArtifactCommit_Leave_00",
				"Cleanup",
				// End of deployment.
			},
		.status_log =
			{
				"downloading",
				"installing",
				"rebooting",
				"installing",
				"success",
			},
		.install_outcome = InstallOutcome::SuccessfulInstall,
		.error_states = {"Sync_Enter_00", "Sync_Leave_00"},
		.generate_idle_sync_scripts = true,
		.stop_after_n_deployments = 2,
	},
};

vector<StateTransitionsTestCase> GenerateStateTransitionsTestCases() {
	return {
		StateTransitionsTestCase {
			.case_name = "Normal_install__no_reboot__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					// Twice, due to the pre-commit status update.
					"installing",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
			.rollback_disabled = true,
			.reboot_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Normal_install__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
			.rollback_disabled = true,

		},

		StateTransitionsTestCase {
			.case_name = "Normal_install",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_Download_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Error_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"Download"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_Download_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"Download"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactInstall_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactInstall"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactInstall_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.spont_reboot_states = {"ArtifactInstall"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactInstall",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactInstall"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactInstall",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"ArtifactInstall"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactReboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactReboot"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactReboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
			.spont_reboot_states = {"ArtifactReboot"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactVerifyReboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactVerifyReboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"ArtifactVerifyReboot"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactRollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",          // <- Fails here
					"ArtifactRollback_Leave_00", // <- No error scripts called here
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactVerifyReboot", "ArtifactRollback"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactRollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot"},
			.spont_reboot_states = {"ArtifactRollback"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactRollbackReboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot", "ArtifactRollbackReboot"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactRollbackReboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot"},
			.spont_reboot_states = {"ArtifactRollbackReboot"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactVerifyRollbackReboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot", "ArtifactVerifyRollbackReboot"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactVerifyRollbackReboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot"},
			.spont_reboot_states = {"ArtifactVerifyRollbackReboot"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactFailure",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactInstall", "ArtifactFailure"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactFailure",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactInstall"},
			.spont_reboot_states = {"ArtifactFailure"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactFailure__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactInstall"},
			.spont_reboot_states = {"ArtifactFailure"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_Cleanup",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot", "Cleanup"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_Cleanup",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot"},
			.spont_reboot_states = {"Cleanup"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_Cleanup__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactVerifyReboot"},
			.spont_reboot_states = {"Cleanup"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactCommit",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactCommit"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactCommit",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"ArtifactCommit"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactCommit__no_reboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactCommit"},
			.reboot_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactCommit__no_reboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"ArtifactCommit"},
			.reboot_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_Download_Enter_00_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"Download_Error_00",
				},
			.status_log =
				{
					"downloading",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"Download_Enter_00"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_Download_Enter_00_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					// "Cleanup", <- No download, no update module to execute
				},
			.status_log =
				{
					"downloading",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"Download_Enter_00"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactInstall_Enter_00_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall_Error_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactInstall_Enter_00"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactInstall_depends_check",
			// This test never reaches the update module so there's nothing to
			// record the state chain.
			.state_chain =
				{
					"Download_Enter_00",
					"Download_Error_00",
				},
			.status_log =
				{
					"downloading",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.device_type_mismatch = true,
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactInstall_Enter_00_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.spont_reboot_states = {"ArtifactInstall_Enter_00"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactInstall_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactInstall_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactInstall_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"ArtifactInstall_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactReboot_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactReboot_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactReboot_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
			.spont_reboot_states = {"ArtifactReboot_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactRollback_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactVerifyReboot", "ArtifactRollback_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactRollback_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot"},
			.spont_reboot_states = {"ArtifactRollback_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactRollbackReboot_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactVerifyReboot", "ArtifactRollbackReboot_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactRollbackReboot_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot"},
			.spont_reboot_states = {"ArtifactRollbackReboot_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactFailure_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactInstall", "ArtifactFailure_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactFailure_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactInstall"},
			.spont_reboot_states = {"ArtifactFailure_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactCommit_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactCommit_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactCommit_Enter_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"ArtifactCommit_Enter_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactCommit_Enter_00__no_reboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactCommit_Enter_00"},
			.reboot_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactCommit_Enter_00__no_reboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"ArtifactCommit_Enter_00"},
			.reboot_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_Download_Leave_00_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"Download_Error_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"Download_Leave_00"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_Download_Leave_00_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"Download_Leave_00"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactInstall_Leave_00_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactInstall_Error_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactInstall_Leave_00"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactInstall_Leave_00_state__no_rollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.spont_reboot_states = {"ArtifactInstall_Leave_00"},
			.rollback_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactInstall_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactInstall_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactInstall_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"ArtifactInstall_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactReboot_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactReboot_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactReboot_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"ArtifactReboot_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactRollback_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactVerifyReboot", "ArtifactRollback_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactRollback_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot"},
			.spont_reboot_states = {"ArtifactRollback_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactRollbackReboot_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactRollbackReboot_Error_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactVerifyReboot", "ArtifactRollbackReboot_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactRollbackReboot_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactVerifyReboot"},
			.spont_reboot_states = {"ArtifactRollbackReboot_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactFailure_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactInstall", "ArtifactFailure_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactFailure_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.error_states = {"ArtifactInstall"},
			.spont_reboot_states = {"ArtifactFailure_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactCommit_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"ArtifactCommit_Error_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactCommit_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactCommit_Leave_00",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"ArtifactCommit_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
			.spont_reboot_states = {"ArtifactCommit_Leave_00"},
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_ArtifactCommit_Leave_00__no_reboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"ArtifactCommit_Error_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"installing", // Really commit
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactCommit_Leave_00"},
			.reboot_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactCommit_Leave_00__no_reboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"ArtifactCommit_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"installing",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
			.spont_reboot_states = {"ArtifactCommit_Leave_00"},
			.reboot_disabled = true,
		},

		StateTransitionsTestCase {
			.case_name = "Break_out_of_error_loop",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",

					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot",
					// Truncated after maximum number of state transitions.
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactVerifyReboot", "ArtifactVerifyRollbackReboot"},
			.error_forever = true,
		},

		StateTransitionsTestCase {
			.case_name = "Break_out_of_spontaneous_reboot_loop",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					// Truncated after maximum number of state transitions.
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactVerifyReboot"},
			.spont_reboot_states = {"ArtifactFailure"},
			.spont_reboot_forever = true,
		},

		StateTransitionsTestCase {
			.case_name = "Hang_in_Download_state",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Error_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.hang_states = {"Download"},
		},

		StateTransitionsTestCase {
			.case_name = "Hang_in_ArtifactInstall",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.hang_states = {"ArtifactInstall"},
		},

		StateTransitionsTestCase {
			.case_name = "Temporary_failure_in_report_sending_after_reboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					// "installing", // Missing because of fail_status_report_status below
					"rebooting",
					"installing",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
			.fail_status_report_count = 10,
			.fail_status_report_status = deployments::DeploymentStatus::Installing,
		},

		StateTransitionsTestCase {
			.case_name = "Permanent_failure_in_report_sending_after_reboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					// "installing", // Missing because of fail_status_report_status below
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.fail_status_report_count = 100,
			.fail_status_report_status = deployments::DeploymentStatus::Installing,
		},

		StateTransitionsTestCase {
			.case_name = "Aborted_update_before_download",
			.status_log =
				{
					// "downloading", // Missing because of fail_status_report_status below
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.fail_status_report_count = 100,
			.fail_status_report_status = deployments::DeploymentStatus::Downloading,
			.fail_status_aborted = true,
			// When aborting an update, it should react immediately.
			.long_retry_times = true,
		},

		StateTransitionsTestCase {
			.case_name = "Aborted_update_before_install",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					// "installing", // Missing because of fail_status_report_status below
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.fail_status_report_count = 100,
			.fail_status_report_status = deployments::DeploymentStatus::Installing,
			.fail_status_aborted = true,
			// When aborting an update, it should react immediately.
			.long_retry_times = true,
		},

		StateTransitionsTestCase {
			.case_name = "Aborted_update_before_reboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					// "rebooting", // Missing because of fail_status_report_status below
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.fail_status_report_count = 100,
			.fail_status_report_status = deployments::DeploymentStatus::Rebooting,
			.fail_status_aborted = true,
			// When aborting an update, it should react immediately.
			.long_retry_times = true,
		},

		StateTransitionsTestCase {
			.case_name = "Killed_in_ArtifactReboot_with_schema_update",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
			.spont_reboot_states = {"ArtifactReboot"},
			.do_schema_update_at_invocation = 1,
		},

		StateTransitionsTestCase {
			.case_name = "Completely_non_writable_database",
			.state_chain =
				{
					// No states at all, because we don't even get to the point
					// of calling update modules.
					"Download_Error_00",
				},
			.status_log =
				{
					"downloading",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.use_non_writable_db_after_n_writes = 0,
		},

		StateTransitionsTestCase {
			.case_name = "Non_writable_database_in_ArtifactInstall_Enter",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.use_non_writable_db_after_n_writes = 4,
		},

		StateTransitionsTestCase {
			.case_name = "Broken_Download",
			.state_chain =
				{
					// No states at all, because we don't even get to the point
					// of calling update modules.
					"Download_Enter_00",
					"Download_Error_00",
				},
			.status_log =
				{
					"downloading",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.broken_download = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_NeedsArtifactReboot",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"NeedsArtifactReboot"},
			.error_forever = true,
		},

		StateTransitionsTestCase {
			.case_name = "Error_in_SupportsRollback",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Error_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"failure",
				},
			.install_outcome = InstallOutcome::UnsuccessfulInstall,
			.error_states = {"ArtifactInstall", "SupportsRollback"},
			.error_forever = true,
		},

		StateTransitionsTestCase {
			.case_name = "Empty_payload_artifact",
			.state_chain =
				{
					"Download_Enter_00", "Download_Leave_00",
					// No visible Cleanup, because there is no Update Module to
					// run. We do enter the state internally though.
				},
			.status_log =
				{
					"downloading",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
			.empty_payload_artifact = true,
		},

		StateTransitionsTestCase {
			.case_name = "Deployment_with_empty_Update_Control",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactVerifyReboot",
					"ArtifactReboot_Leave_00",
					"ArtifactCommit_Enter_00",
					"ArtifactCommit",
					"ArtifactCommit_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"installing",
					"success",
				},
			.install_outcome = InstallOutcome::SuccessfulInstall,
			.spont_reboot_states = {"ArtifactReboot"},
			.update_control_string = "{}",
		},

		StateTransitionsTestCase {
			.case_name = "Deployment_with_Update_Control",
			.state_chain =
				{
					"Download_Enter_00",
					"ProvidePayloadFileSizes",
					"Download",
					"Download_Leave_00",
					"ArtifactInstall_Enter_00",
					"ArtifactInstall",
					"ArtifactInstall_Leave_00",
					"ArtifactReboot_Enter_00",
					"ArtifactReboot",
					"ArtifactReboot_Error_00",
					"ArtifactRollback_Enter_00",
					"ArtifactRollback",
					"ArtifactRollback_Leave_00",
					"ArtifactRollbackReboot_Enter_00",
					"ArtifactRollbackReboot",
					"ArtifactVerifyRollbackReboot",
					"ArtifactRollbackReboot_Leave_00",
					"ArtifactFailure_Enter_00",
					"ArtifactFailure",
					"ArtifactFailure_Leave_00",
					"Cleanup",
				},
			.status_log =
				{
					"downloading",
					"installing",
					"rebooting",
					"failure",
				},
			.install_outcome = InstallOutcome::SuccessfulRollback,
			.spont_reboot_states = {"ArtifactReboot"},
			.update_control_string = R"({"something_update_control_related":true})",
		},
	};
}

vector<string> MakeTestArtifactScripts(
	const StateTransitionsTestCase &test_case, const string &tmpdir, const string &log_path);

class StateDeathTest : public testing::TestWithParam<StateTransitionsTestCase> {
public:
	void SetUp() override {
		{
			vector<string> args {
				"mender-artifact",
				"write",
				"module-image",
				"--type",
				"test-module",
				"--compatible-types",
				"test-type",
				"--artifact-name",
				"artifact-name",
				"--output-path",
				ArtifactPath(),
			};

			const auto scripts = MakeTestArtifactScripts(
				GetParam(), tmpdir.Path(), path::Join(tmpdir.Path(), "state.log"));
			for (const auto &script : scripts) {
				args.push_back("--script");
				args.push_back(script);
			}

			processes::Process proc(args);
			auto err = proc.Run();
			ASSERT_EQ(err, error::NoError) << err.String();
		}
		{
			processes::Process proc({
				"mender-artifact",
				"write",
				"bootstrap-artifact",
				"--compatible-types",
				"test-type",
				"--artifact-name",
				"artifact-name",
				"--output-path",
				EmptyPayloadArtifactPath(),
			});
			auto err = proc.Run();
			ASSERT_EQ(err, error::NoError) << err.String();
		}
	}

	string ArtifactPath() const {
		return path::Join(tmpdir.Path(), "artifact.mender");
	}

	string EmptyPayloadArtifactPath() const {
		return path::Join(tmpdir.Path(), "bootstrap.mender");
	}

public:
	mtesting::TemporaryDirectory tmpdir;
};

INSTANTIATE_TEST_SUITE_P(
	Regular_Non_Update_State_Tests,
	StateDeathTest,
	::testing::ValuesIn(idle_and_sync_test_cases),
	[](const testing::TestParamInfo<StateTransitionsTestCase> &test_case) {
		return test_case.param.case_name;
	});


INSTANTIATE_TEST_SUITE_P(
	,
	StateDeathTest,
	::testing::ValuesIn(GenerateStateTransitionsTestCases()),
	[](const testing::TestParamInfo<StateTransitionsTestCase> &test_case) {
		return test_case.param.case_name;
	});

void MakeTestUpdateModule(
	const StateTransitionsTestCase &test_case, const string &path, const string &log_path) {
	ofstream f(path);

	f << R"(#!/bin/bash
case "$1" in
    SupportsRollback|NeedsArtifactReboot)
        # Ignore these two, they are not important for the flow.
        ;;
    *)
        echo "$1" >> )"
	  << log_path << R"(
        ;;
esac

if [ "$1" = "SupportsRollback" ]; then
    echo )"
	  << (test_case.rollback_disabled ? "No" : "Yes") << R"(
fi

if [ "$1" = "NeedsArtifactReboot" ]; then
    echo )"
	  << (test_case.reboot_disabled ? "No" : "Yes") << R"(
fi

if [ "$1" = "ProvidePayloadFileSizes" ]; then
    echo )"
	  << ProvideFileSizesToString(test_case.provide_file_sizes) << R"(
fi
)";

	// Kill parent (mender) in specified state
	for (auto &state : test_case.spont_reboot_states) {
		f << R"(
if [ "$1" = ")"
		  << state << R"(" ]; then
)";

		// Prevent spontaneous rebooting forever.
		if (!test_case.spont_reboot_forever) {
			f << R"(
    if [ ! -e "$2/tmp/$1.already-killed" ]; then
        touch "$2/tmp/$1.already-killed"
        kill -9 $PPID
    fi
)";
		} else {
			f << R"(
    kill -9 $PPID
)";
		}
		f << R"(
fi
)";
	}

	// Produce error in specified state.
	for (auto &state : test_case.error_states) {
		f << R"(
if [ "$1" = ")"
		  << state << R"(" ]; then
)";
		// Prevent returning same error forever.
		if (!test_case.error_forever) {
			f << R"(
    if [ ! -e "$2/tmp/$1.already-errored" ]; then
        touch "$2/tmp/$1.already-errored"
        exit 1
    fi
)";
		} else {
			f << R"(
    exit 1
)";
		}
		f << R"(
fi
)";
	}

	// Hang in specified state
	for (auto &state : test_case.hang_states) {
		f << R"(
if [ "$1" = ")"
		  << state << R"(" ]; then
    sleep 120
fi
)";
	}

	ASSERT_TRUE(f.good());
	error_code ec;
	fs::permissions(path, fs::perms::owner_all, ec);
	ASSERT_FALSE(ec);
}

vector<string> MakeTestArtifactScripts(
	const StateTransitionsTestCase &test_case, const string &tmpdir, const string &log_path) {
	const auto idle_sync_scripts_list = vector<string> {
		"Idle",
		"Sync",
	};
	auto state_script_list = vector<string> {
		"Download",
		"ArtifactInstall",
		"ArtifactReboot",
		"ArtifactCommit",
		"ArtifactRollback",
		"ArtifactRollbackReboot",
		"ArtifactFailure",
	};

	if (test_case.generate_idle_sync_scripts) {
		state_script_list.insert(
			state_script_list.end(),
			idle_sync_scripts_list.cbegin(),
			idle_sync_scripts_list.cend());
	}

	const auto artifact_scripts_dir = path::Join(tmpdir, "artifact-scripts");
	const auto rootfs_scripts_dir = path::Join(tmpdir, "rootfs-scripts");
	error_code ec;
	fs::create_directories(artifact_scripts_dir, ec);
	EXPECT_FALSE(ec) << ec.message() << ": " << artifact_scripts_dir;
	fs::create_directories(rootfs_scripts_dir, ec);
	EXPECT_FALSE(ec) << ec.message() << ": " << rootfs_scripts_dir;

	{
		ofstream version_file(path::Join(rootfs_scripts_dir, "version"));
		version_file << "3";
		EXPECT_TRUE(version_file.good());
	}

	vector<string> artifact_scripts;

	for (const auto &state : state_script_list) {
		for (const auto &enter_leave : vector<string> {"Enter", "Leave", "Error"}) {
			const auto script_file = state + "_" + enter_leave + "_00";
			string script_path;
			if (state.substr(0, 8) == "Artifact") {
				script_path = path::Join(artifact_scripts_dir, script_file);
				artifact_scripts.push_back(script_path);
			} else {
				script_path = path::Join(rootfs_scripts_dir, script_file);
			}

			ofstream f(script_path);

			f << R"(#!/bin/bash
echo )" << script_file
			  << " >> " << log_path << R"(
)";

			f << R"(
echo )" << script_file;

			auto &error_states = test_case.error_states;
			if (find(error_states.begin(), error_states.end(), script_file) != error_states.end()) {
				if (!test_case.error_forever) {
					f << R"(
if [ ! -e ")" << tmpdir
					  << "/" << script_file << R"(.already-errored" ]; then
    touch ")" << tmpdir
					  << "/" << script_file << R"(.already-errored"
    exit 1
fi
)";
				} else {
					f << R"(
exit 1
)";
				}
			}

			auto &spont_reboot_states = test_case.spont_reboot_states;
			if (find(spont_reboot_states.begin(), spont_reboot_states.end(), script_file)
				!= spont_reboot_states.end()) {
				if (!test_case.spont_reboot_forever) {
					f << R"(
if [ ! -e ")" << tmpdir
					  << "/" << script_file << R"(.already-killed" ]; then
    touch ")" << tmpdir
					  << "/" << script_file << R"(.already-killed"
    kill -9 $PPID
fi
)";
				} else {
					f << R"(
kill -9 $PPID
)";
				}
			}

			f << R"(
exit 0
)";

			EXPECT_TRUE(f.good());

			// Make the script executable
			int ret {chmod(script_path.c_str(), S_IRUSR | S_IWUSR | S_IXUSR)};
			EXPECT_EQ(ret, 0);
		}
	}

	return artifact_scripts;
}

class NoopInventoryClient : virtual public inventory::InventoryAPI {
	error::Error PushData(
		const string &inventory_generators_dir,
		events::EventLoop &loop,
		api::Client &client,
		inventory::APIResponseHandler api_handler) override {
		api_handler(inventory::APIResponse {nullopt, nullopt, error::NoError});
		return error::NoError;
	}
	void ClearDataCache() override {
	}
};

class NoopDeploymentClient : virtual public deployments::DeploymentAPI {
public:
	error::Error CheckNewDeployments(
		context::MenderContext &ctx,
		api::Client &client,
		deployments::CheckUpdatesAPIResponseHandler api_handler) override {
		api_handler(nullopt);
		return error::NoError;
	}

	error::Error PushStatus(
		const string &deployment_id,
		deployments::DeploymentStatus status,
		const string &substate,
		api::Client &client,
		deployments::StatusAPIResponseHandler api_handler) override {
		api_handler(deployments::StatusAPIResponse {nullopt, nullopt, error::NoError});
		return error::NoError;
	}

	error::Error PushLogs(
		const string &deployment_id,
		const string &log_file_path,
		api::Client &client,
		deployments::LogsAPIResponseHandler api_handler) override {
		api_handler(deployments::LogsAPIResponse {nullopt, nullopt, error::NoError});
		return error::NoError;
	}
};

class TestDeploymentClient : virtual public deployments::DeploymentAPI {
public:
	TestDeploymentClient(
		events::EventLoop &event_loop,
		const string &artifact_url,
		string status_log_path = "",
		int fail_status_report_count = 0,
		deployments::DeploymentStatus fail_status_report_status =
			deployments::DeploymentStatus::Success,
		bool fail_status_aborted = false) :
		event_loop_(event_loop),
		artifact_url_(artifact_url),
		status_log_path_(status_log_path),
		fail_status_report_count_(fail_status_report_count),
		fail_status_report_status_(fail_status_report_status),
		fail_status_aborted_(fail_status_aborted) {
	}

	error::Error CheckNewDeployments(
		context::MenderContext &ctx,
		api::Client &client,
		deployments::CheckUpdatesAPIResponseHandler api_handler) override {
		event_loop_.Post([this, api_handler]() {
			auto exp = json::Load(R"({
  "id": ")" + deployment_id_ + R"(",
  "artifact": {
    "artifact_name": "artifact-name",
    "source": {
      "uri": ")" + artifact_url_ + R"(",
      "expire": "2016-03-11T13:03:17.063493443Z"
    },
    "device_types_compatible": [
      "test-type"
    ]
  }
})");
			api_handler(exp.value());
		});
		return error::NoError;
	}
	error::Error PushStatus(
		const string &deployment_id,
		deployments::DeploymentStatus status,
		const string &substate,
		api::Client &client,
		deployments::StatusAPIResponseHandler api_handler) override {
		event_loop_.Post([this, status, api_handler]() {
			if (fail_status_report_status_ == status && fail_status_report_count_ > 0) {
				fail_status_report_count_--;
				if (fail_status_aborted_) {
					api_handler(deployments::StatusAPIResponse {
						nullopt,
						nullopt,
						deployments::MakeError(
							deployments::DeploymentAbortedError, "Cannot send status")});
				} else {
					api_handler(deployments::StatusAPIResponse {
						nullopt,
						nullopt,
						error::Error(
							make_error_condition(errc::host_unreachable), "Cannot send status")});
				}
				return;
			}

			if (status_log_path_ != "") {
				ofstream f(status_log_path_, ios::out | ios::app);
				f << deployments::DeploymentStatusString(status) << endl;
				if (!f) {
					api_handler(deployments::StatusAPIResponse {
						nullopt,
						nullopt,
						error::Error(
							generic_category().default_error_condition(errno),
							"Could not do PushStatus")});
				}
			}

			api_handler(deployments::StatusAPIResponse {nullopt, nullopt, error::NoError});
		});
		return error::NoError;
	}

	error::Error PushLogs(
		const string &deployment_id,
		const string &log_file_path,
		api::Client &client,
		deployments::LogsAPIResponseHandler api_handler) override {
		// Just save the log file name so they can be checked later.
		log_files.push_back(log_file_path);
		event_loop_.Post([api_handler]() {
			api_handler(deployments::StatusAPIResponse {nullopt, nullopt, error::NoError});
		});
		return error::NoError;
	}

	void SetDeploymentId(const string &id) {
		deployment_id_ = id;
	}

	vector<string> log_files;

private:
	events::EventLoop &event_loop_;
	string artifact_url_;
	string status_log_path_;

	int fail_status_report_count_;
	deployments::DeploymentStatus fail_status_report_status_;
	bool fail_status_aborted_;

	string deployment_id_ {DEPLOYMENT_ID};
};

// Normal DB, but writes can fail.
class NonWritableDb : virtual public kv_db::KeyValueDatabase {
public:
	NonWritableDb(kv_db::KeyValueDatabase &db, int max_write_count) :
		db_(db),
		write_count_(0),
		max_write_count_(max_write_count) {
	}

	expected::ExpectedBytes Read(const string &key) override {
		return db_.Read(key);
	}

	error::Error Write(const string &key, const vector<uint8_t> &value) override {
		if (write_count_++ >= max_write_count_) {
			return error::Error(make_error_condition(errc::io_error), "Test error");
		}
		return db_.Write(key, value);
	}

	error::Error Remove(const string &key) override {
		if (write_count_++ >= max_write_count_) {
			return error::Error(make_error_condition(errc::io_error), "Test error");
		}
		return db_.Remove(key);
	}

	error::Error WriteTransaction(function<error::Error(Transaction &)> txnFunc) override {
		if (write_count_++ >= max_write_count_) {
			return error::Error(make_error_condition(errc::io_error), "Test error");
		}
		return db_.WriteTransaction(txnFunc);
	}

	error::Error ReadTransaction(function<error::Error(Transaction &)> txnFunc) override {
		return db_.ReadTransaction(txnFunc);
	}

private:
	kv_db::KeyValueDatabase &db_;
	int write_count_;
	int max_write_count_;
};

class NonWritableDbContext : public context::MenderContext {
public:
	NonWritableDbContext(conf::MenderConfig &config, int max_write_count) :
		MenderContext(config),
		test_db_(MenderContext::GetMenderStoreDB(), max_write_count) {
	}

	kv_db::KeyValueDatabase &GetMenderStoreDB() override {
		return test_db_;
	}

private:
	NonWritableDb test_db_;
};

void StateTransitionsTestSubProcess(
	const string &tmpdir, const StateDeathTest &test, const string &status_log_path) {
	// Make sure everything is destroyed before calling exit() below. This is important due to
	// exit handlers which should not be invoked while these objects are still alive.
	{
		conf::MenderConfig config {};
		config.update_poll_interval_seconds = 1;
		config.module_timeout_seconds = 2;
		config.paths.SetDataStore(tmpdir);
		config.paths.SetArtScriptsPath(path::Join(tmpdir, "artifact-scripts"));
		config.paths.SetRootfsScriptsPath(path::Join(tmpdir, "rootfs-scripts"));
		config.retry_poll_count = 10;

		string artifact_path;
		if (test.GetParam().empty_payload_artifact) {
			artifact_path = test.EmptyPayloadArtifactPath();
		} else {
			artifact_path = test.ArtifactPath();
		}

		mtesting::HttpFileServer server(path::DirName(artifact_path));
		string artifact_url;
		if (test.GetParam().broken_download) {
			artifact_url = http::JoinUrl(server.GetBaseUrl(), "nonexisting.mender");
		} else {
			artifact_url = http::JoinUrl(server.GetBaseUrl(), path::BaseName(artifact_path));
		}

		if (test.GetParam().device_type_mismatch) {
			ofstream f(path::Join(tmpdir, "device_type"));
			f << "device_type=mismatch-type\n";
			ASSERT_IN_DEATH_TEST(f.good()) << "Failed to write mismatching device type";
		}

		unique_ptr<context::MenderContext> main_context;
		if (test.GetParam().use_non_writable_db_after_n_writes >= 0) {
			main_context.reset(new NonWritableDbContext(
				config, test.GetParam().use_non_writable_db_after_n_writes));
		} else {
			main_context.reset(new context::MenderContext(config));
		}
		auto err = main_context->Initialize();
		ASSERT_IN_DEATH_TEST(err == error::NoError) << err.String();
		main_context->GetConfig().paths.SetModulesPath(tmpdir);
		main_context->GetConfig().paths.SetModulesWorkPath(tmpdir);

		mtesting::TestEventLoop event_loop;

		Context ctx(*main_context, event_loop);

		// Avoid waiting by setting a short retry time.
		chrono::milliseconds retry_time = chrono::milliseconds(1);
		if (test.GetParam().long_retry_times) {
			retry_time = chrono::minutes(1);
		}
		StateMachine state_machine(ctx, event_loop, retry_time);
		state_machine.LoadStateFromDb();

		ctx.inventory_client = make_shared<NoopInventoryClient>();
		ctx.deployment_client = make_shared<TestDeploymentClient>(
			event_loop,
			artifact_url,
			status_log_path,
			test.GetParam().fail_status_report_count,
			test.GetParam().fail_status_report_status,
			test.GetParam().fail_status_aborted);

		if (test.GetParam().stop_after_n_deployments > 1) {
#ifndef NDEBUG
			state_machine.StopAfterDeployments(test.GetParam().stop_after_n_deployments);
#endif
		} else {
			state_machine.StopAfterDeployment();
		}
		err = state_machine.Run();
		ASSERT_IN_DEATH_TEST(err == error::NoError) << err.String();
	}

	std::exit(0);
}

void DoSchemaUpdate(kv_db::KeyValueDatabase &db) {
	auto exp_bytes = db.Read(context::MenderContext::state_data_key);
	ASSERT_TRUE(exp_bytes) << exp_bytes.error();
	string state_data = common::StringFromByteVector(exp_bytes.value());

	// Store the original under the uncommitted key.
	auto err = db.Write(
		context::MenderContext::state_data_key_uncommitted,
		common::ByteVectorFromString(state_data));
	ASSERT_EQ(err, error::NoError);

	regex version_matcher {R"("Version": *[0-9]+)"};
	state_data = regex_replace(state_data, version_matcher, R"("Version":9876)");

	// Store the incompatible version under the original key, pretending that this is an upgrade
	// from a version we don't support.
	err =
		db.Write(context::MenderContext::state_data_key, common::ByteVectorFromString(state_data));
	ASSERT_EQ(err, error::NoError);
}


TEST_P(StateDeathTest, StateTransitionsTest) {
	// This test requires "fast" mode. The reason is that since we need to run a sub process
	// multiple times, we have to use "fork", we cannot use the start-from-scratch approach that
	// the "threadsafe" mode uses. Also, our temporary directory would not be the same across
	// multiple runs. See "Death Test Styles" in the Googletest documentation for more
	// information.
	GTEST_FLAG_SET(death_test_style, "fast");

#ifdef NDEBUG
	if (GetParam().stop_after_n_deployments > 1) {
		GTEST_SKIP() << "Stopping after N deployments requires debug mode";
	}
#endif

	{
		ofstream f(path::Join(tmpdir.Path(), "device_type"));
		f << "device_type=test-type\n";
		ASSERT_TRUE(f.good());
	}

	const string state_log_path = path::Join(tmpdir.Path(), "state.log");
	const string update_module_name = "test-module";
	const string update_module_path = path::Join(tmpdir.Path(), update_module_name);
	const string status_log_path = path::Join(tmpdir.Path(), "status.log");

	{
		// We don't care about existence, only content, so pre-create these files in order
		// to make FileContains* functions happy.
		ofstream state_log(state_log_path);
		ofstream status_log(status_log_path);
	}

	const auto artifact_scripts =
		MakeTestArtifactScripts(GetParam(), tmpdir.Path(), state_log_path);
	ASSERT_FALSE(::testing::Test::HasFailure());

	MakeTestUpdateModule(GetParam(), update_module_path, state_log_path);
	ASSERT_FALSE(::testing::Test::HasFailure());

	conf::MenderConfig config {};
	config.paths.SetDataStore(tmpdir.Path());

	context::MenderContext main_context(config);
	auto err = main_context.Initialize();
	ASSERT_EQ(err, error::NoError) << err.String();

	// Initialize initial database content.
	err = main_context.GetMenderStoreDB().Write(
		main_context.artifact_name_key, common::ByteVectorFromString("old_name"));
	ASSERT_EQ(err, error::NoError) << err.String();

	int count = 0;
	for (bool finished = false; !finished; count++) {
		if (GetParam().do_schema_update_at_invocation == count) {
			DoSchemaUpdate(main_context.GetMenderStoreDB());
			ASSERT_FALSE(::testing::Test::HasFailure());
		}

		// Annoyingly, this doesn't produce any output when a later assert fails. To enable
		// output, change the debug variable below. Be aware that this in itself causes the
		// test to fail, but you can still see the results of later asserts.
		EXPECT_EXIT(
			StateTransitionsTestSubProcess(tmpdir.Path(), *this, status_log_path),
			[&finished](int arg) {
				bool debug = false;
				bool clean_exit = testing::ExitedWithCode(0)(arg);
				bool sig_kill = testing::KilledBySignal(9)(arg);
				finished = clean_exit || !sig_kill;
				bool success = clean_exit || sig_kill;
				return !debug && success;
			},
			"");
		ASSERT_LT(count, 100) << "Looped too many times";

		if (GetParam().update_control_string != "") {
			// Do this in-between restarts.
			err = main_context.GetMenderStoreDB().Write(
				main_context.update_control_maps,
				common::ByteVectorFromString(GetParam().update_control_string));
			ASSERT_EQ(err, error::NoError);
		}
	}

	auto exp_provides = main_context.LoadProvides();
	ASSERT_TRUE(exp_provides) << exp_provides.error().String();
	auto &provides = exp_provides.value();

	switch (GetParam().install_outcome) {
	case InstallOutcome::SuccessfulInstall:
		EXPECT_EQ(provides["artifact_name"], "artifact-name");
		break;
	case InstallOutcome::SuccessfulRollback:
		EXPECT_EQ(provides["artifact_name"], "old_name");
		break;
	case InstallOutcome::UnsuccessfulInstall:
		EXPECT_EQ(
			provides["artifact_name"], "artifact-name" + main_context.broken_artifact_name_suffix);
		break;
	}

	auto content = common::JoinStrings(GetParam().state_chain, "\n") + "\n";
	if (content == "\n") {
		content = "";
	}
	EXPECT_TRUE(mtesting::FileContainsExactly(state_log_path, content));

	content = common::JoinStrings(GetParam().status_log, "\n") + "\n";
	if (content == "\n") {
		content = "";
	}
	EXPECT_TRUE(mtesting::FileContainsExactly(status_log_path, content));
}

class StateTestWithArtifact : public testing::Test {
public:
	void SetUp() override {
		{
			processes::Process proc({
				"mender-artifact",
				"write",
				"module-image",
				"--type",
				"test-module",
				"--compatible-types",
				"test-type",
				"--artifact-name",
				"artifact-name",
				"--output-path",
				ArtifactPath(),
			});
			ASSERT_EQ(proc.Run(), error::NoError);
		}
		{
			processes::Process proc({
				"mender-artifact",
				"write",
				"module-image",
				"--type",
				context::MenderContext::orchestrator_manifest_payload_type,
				"--compatible-types",
				"test-type",
				"--artifact-name",
				"artifact-name",
				"--output-path",
				ManifestArtifactPath(),
			});
			ASSERT_EQ(proc.Run(), error::NoError);
		}
	}

	string ArtifactPath() const {
		return path::Join(tmpdir_.Path(), "artifact.mender");
	}

	string ManifestArtifactPath() const {
		return path::Join(tmpdir_.Path(), "manifest.mender");
	}

private:
	mtesting::TemporaryDirectory tmpdir_;
};

TEST_F(StateTestWithArtifact, DeploymentLogging) {
	mtesting::TemporaryDirectory tmpdir;
	conf::MenderConfig config;
	config.paths.SetDataStore(tmpdir.Path());
	context::MenderContext main_context(config);
	auto err = main_context.Initialize();
	mtesting::TestEventLoop event_loop;
	Context ctx(main_context, event_loop);

	mtesting::HttpFileServer server(path::DirName(ArtifactPath()));

	auto artifact_url = http::JoinUrl(server.GetBaseUrl(), path::BaseName(ArtifactPath()));
	auto deployment_client = make_shared<TestDeploymentClient>(event_loop, artifact_url);
	ctx.deployment_client = deployment_client;
	ctx.inventory_client = make_shared<NoopInventoryClient>();

	{
		StateMachine state_machine(ctx, event_loop);
		state_machine.StopAfterDeployment();
		err = state_machine.Run();
		EXPECT_EQ(err, error::NoError);
	}

	auto deployment_log = path::Join(tmpdir.Path(), "deployments.0000." DEPLOYMENT_ID ".log");
	EXPECT_TRUE(mtesting::FileContains(deployment_log, "Running mender-update"));
	EXPECT_TRUE(
		mtesting::FileContains(deployment_log, "Deployment with ID " DEPLOYMENT_ID " started"));
	ASSERT_EQ(deployment_client->log_files.size(), 1);
	EXPECT_EQ(deployment_client->log_files[0], deployment_log);

	deployment_client->log_files.clear();

	string new_id = "01234567-89ab-cdef-0123-456789abcdef";
	deployment_client->SetDeploymentId(new_id);

	{
		StateMachine state_machine(ctx, event_loop);
		state_machine.StopAfterDeployment();
		err = state_machine.Run();
		EXPECT_EQ(err, error::NoError);
	}

	deployment_log = path::Join(tmpdir.Path(), "deployments.0000." + new_id + ".log");
	EXPECT_TRUE(mtesting::FileContains(deployment_log, "Running mender-update"));
	EXPECT_TRUE(
		mtesting::FileContains(deployment_log, "Deployment with ID " + new_id + " started"));
	ASSERT_EQ(deployment_client->log_files.size(), 1);
	EXPECT_EQ(deployment_client->log_files[0], deployment_log);

	auto moved_deployment_log = path::Join(tmpdir.Path(), "deployments.0001." DEPLOYMENT_ID ".log");
	EXPECT_TRUE(mtesting::FileContains(moved_deployment_log, "Running mender-update"));
	EXPECT_TRUE(mtesting::FileContains(
		moved_deployment_log, "Deployment with ID " DEPLOYMENT_ID " started"));

	auto no_such_deployment_log =
		path::Join(tmpdir.Path(), "deployments.0002." DEPLOYMENT_ID ".log");
	EXPECT_FALSE(mtesting::FileContains(no_such_deployment_log, "Running mender-update"));
}

static void WriteNoopUpdateModule(const string &modules_path, const string &payload_type) {
	auto module = path::Join(modules_path, payload_type);
	{
		ofstream f(module);
		f << "#!/bin/sh\n"
			 "case \"$1\" in\n"
			 "  SupportsRollback) echo No ;;\n"
			 "  NeedsArtifactReboot) echo No ;;\n"
			 "esac\n"
			 "exit 0\n";
	}
	chmod(module.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
}

static void ExpectManagedDeployment(
	const string &configured_tier, const string &artifact_path, bool expect_success) {
	mtesting::TemporaryDirectory tmpdir;
	mtesting::TemporaryDirectory topology_dir;

	// System tier resolves compatible type from topology system_type, standard from device_type.
	{
		ofstream f(path::Join(tmpdir.Path(), "device_type"));
		f << "device_type=test-type\n";
	}
	{
		ofstream f(path::Join(topology_dir.Path(), "topology.yaml"));
		f << "system_type: test-type\n";
	}
	setenv("MENDER_ORCHESTRATOR_TOPOLOGY_DIR", topology_dir.Path().c_str(), 1);

	conf::MenderConfig config;
	config.paths.SetDataStore(tmpdir.Path());
	config.paths.SetModulesPath(tmpdir.Path());
	config.paths.SetModulesWorkPath(tmpdir.Path());
	config.device_tier = configured_tier;

	context::MenderContext main_context(config);
	auto err = main_context.Initialize();
	ASSERT_EQ(err, error::NoError);
	err = main_context.GetMenderStoreDB().Write(
		main_context.artifact_name_key, common::ByteVectorFromString("original"));
	ASSERT_EQ(err, error::NoError);
	WriteNoopUpdateModule(tmpdir.Path(), "test-module");
	WriteNoopUpdateModule(
		tmpdir.Path(), context::MenderContext::orchestrator_manifest_payload_type);

	mtesting::TestEventLoop event_loop;
	Context ctx(main_context, event_loop);

	mtesting::HttpFileServer server(path::DirName(artifact_path));
	auto artifact_url = http::JoinUrl(server.GetBaseUrl(), path::BaseName(artifact_path));
	auto status_log = path::Join(tmpdir.Path(), "status.log");
	auto deployment_client =
		make_shared<TestDeploymentClient>(event_loop, artifact_url, status_log);
	ctx.deployment_client = deployment_client;
	ctx.inventory_client = make_shared<NoopInventoryClient>();

	StateMachine state_machine(ctx, event_loop);
	state_machine.StopAfterDeployment();
	err = state_machine.Run();
	ASSERT_EQ(err, error::NoError);
	unsetenv("MENDER_ORCHESTRATOR_TOPOLOGY_DIR");

	EXPECT_EQ(mtesting::FileContains(status_log, "success"), expect_success);
}

TEST_F(StateTestWithArtifact, SystemTierRejectsNonManifestArtifact) {
	ExpectManagedDeployment(device_tier::kSystem, ArtifactPath(), false);
}

// Same artifact installs on standard tier, so the system-tier failure above is the guard.
TEST_F(StateTestWithArtifact, StandardTierInstallsNonManifestArtifact) {
	ExpectManagedDeployment(device_tier::kStandard, ArtifactPath(), true);
}

#ifdef MENDER_USE_YAML_CPP
// Manifest deployment reaches topology system_type resolution, which is YAML-only.
TEST_F(StateTestWithArtifact, SystemTierInstallsManifestArtifact) {
	ExpectManagedDeployment(device_tier::kSystem, ManifestArtifactPath(), true);
}
#endif // MENDER_USE_YAML_CPP

class StateTests : public testing::Test {
public:
	StateTests() :
		event_loop_ {chrono::seconds {3}} {
	}

	void SetUp() override {
		config_.paths.SetDataStore(tmpdir_.Path());
		main_context_ = make_unique<context::MenderContext>(config_);
		main_context_->GetConfig().retry_poll_count = 100;
		main_context_->GetConfig().retry_poll_interval_seconds = 3600;
		auto err = main_context_->Initialize();
		ctx_ = make_unique<Context>(*main_context_, event_loop_);
	}

protected:
	void ExpectLogsInOrder(
		const std::string &output, const std::vector<std::string> &logs_in_order) {
		size_t last_pos = 0;
		for (const auto &log : logs_in_order) {
			size_t current_pos = output.find(log, last_pos);
			EXPECT_NE(current_pos, std::string::npos)
				<< "Could not find log: '" << log << "' after previous match.";
			if (current_pos == std::string::npos) {
				return;
			}
			last_pos = current_pos + log.length();
		}
	}

	mtesting::TemporaryDirectory tmpdir_;
	conf::MenderConfig config_;
	unique_ptr<context::MenderContext> main_context_;
	mtesting::TestEventLoop event_loop_;
	unique_ptr<Context> ctx_;
	testing::StrictMock<MockPoster> poster_;
};
class PollForDeploymentStateTests : public StateTests {
public:
	PollForDeploymentStateTests() :
		state_(3600, 0) {
	}

protected:
	void callPollForDeploymentStateHandler(
		mender::update::deployments::CheckUpdatesAPIResponse response) {
		state_.CheckNewDeploymentsHandler(*ctx_, poster_, response);
		ctx_->deployment_timer.Cancel();
	}

	PollForDeploymentState state_;
};

TEST_F(PollForDeploymentStateTests, TooManyRequests_SecondsInRetryAfterHeader) {
	http::Transaction::HeaderMap headers;
	headers.insert({"Retry-After", "3600"});

	mender::update::deployments::CheckUpdatesAPIResponse resp =
		expected::unexpected(mender::update::deployments::CheckUpdatesAPIResponseError {
			http::StatusTooManyRequests,
			headers,
			MakeError(mender::update::deployments::TooManyRequestsError, "Too many requests")});

	mender::update::deployments::CheckUpdatesAPIResponse resp_unauthorized =
		expected::unexpected(mender::update::deployments::CheckUpdatesAPIResponseError {
			http::StatusUnauthorized,
			headers,
			MakeError(mender::update::deployments::BadResponseError, "doesn't matter")});

	EXPECT_CALL(poster_, PostEvent(StateEvent::Failure)).Times(6);
	testing::internal::CaptureStderr();

	callPollForDeploymentStateHandler(resp_unauthorized);
	callPollForDeploymentStateHandler(resp);
	callPollForDeploymentStateHandler(resp_unauthorized);
	callPollForDeploymentStateHandler(resp_unauthorized);
	callPollForDeploymentStateHandler(resp_unauthorized);
	callPollForDeploymentStateHandler(resp);

	auto output = testing::internal::GetCapturedStderr();
	// make sure that retrying with Retry-After does not modify normal
	// exponential backoff behavior
	ExpectLogsInOrder(
		output,
		{
			"Retrying deployment polling in 60 second",
			"Retrying deployment polling in 3600 second",
			"Retrying deployment polling in 60 second",
			"Retrying deployment polling in 60 second",
			"Retrying deployment polling in 120 second",
			"Retrying deployment polling in 3600 second",
		});
}

TEST_F(PollForDeploymentStateTests, TooManyRequests_DateInRetryAfterHeader) {
	time_t now = time(nullptr);
	time_t futureTime = now + 1298;
	struct tm *tm_ptr = gmtime(&futureTime);
	char buffer[100];
	// HTTP Format, e.g. "Wed, 21 Oct 2015 07:28:00 GMT", 1298 seconds from now
	strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", tm_ptr);
	string httpDate(buffer);

	http::Transaction::HeaderMap headers;
	headers.insert({"Retry-After", httpDate});

	mender::update::deployments::CheckUpdatesAPIResponse resp =
		expected::unexpected(mender::update::deployments::CheckUpdatesAPIResponseError {
			http::StatusTooManyRequests,
			headers,
			MakeError(mender::update::deployments::TooManyRequestsError, "Too many requests")});

	EXPECT_CALL(poster_, PostEvent(StateEvent::Failure)).Times(1);
	testing::internal::CaptureStderr();

	callPollForDeploymentStateHandler(resp);

	auto output = testing::internal::GetCapturedStderr();
	// make sure that retrying with Retry-After does not modify normal
	// exponential backoff behavior
	ExpectLogsInOrder(
		output,
		{
			"Retrying deployment polling in 1298 second",
		});
}

TEST_F(PollForDeploymentStateTests, TooManyRequests_NoRetryAfterHeader) {
	http::Transaction::HeaderMap headers;

	mender::update::deployments::CheckUpdatesAPIResponse resp =
		expected::unexpected(mender::update::deployments::CheckUpdatesAPIResponseError {
			http::StatusTooManyRequests,
			headers,
			MakeError(mender::update::deployments::TooManyRequestsError, "Too many requests")});

	int iterations = 7;
	EXPECT_CALL(poster_, PostEvent(StateEvent::Failure)).Times(iterations);
	testing::internal::CaptureStderr();

	for (int i = 0; i < iterations; i++) {
		callPollForDeploymentStateHandler(resp);
	}

	auto output = testing::internal::GetCapturedStderr();

	// Make sure that normal exponential backoff is used
	ExpectLogsInOrder(
		output,
		{
			"Retrying deployment polling in 60 second",
			"Retrying deployment polling in 60 second",
			"Retrying deployment polling in 60 second",
			"Retrying deployment polling in 120 second",
			"Retrying deployment polling in 120 second",
			"Retrying deployment polling in 120 second",
			"Retrying deployment polling in 240 second",
		});
}

TEST(SignalHandlingTests, SigquitHandlingTest) {
	mtesting::TemporaryDirectory tmpdir;
	conf::MenderConfig config {};
	config.paths.SetDataStore(tmpdir.Path());

	context::MenderContext main_context {config};
	auto err = main_context.Initialize();
	mtesting::TestEventLoop event_loop {chrono::seconds {3}};
	Context ctx {main_context, event_loop};

	ctx.deployment_client = make_shared<NoopDeploymentClient>();
	ctx.inventory_client = make_shared<NoopInventoryClient>();

	events::Timer signal_timer {event_loop};
	signal_timer.AsyncWait(chrono::seconds {1}, [](error::Error err) { raise(SIGQUIT); });

	StateMachine state_machine {ctx, event_loop};
	err = state_machine.Run();
	ASSERT_EQ(err, error::NoError);

	// Nothing more to check here, either SIGQUIT is handled properly and
	// terminates the loop or the TestEventLoop's timer kicks in and marks this
	// test as timing out and thus failing.
}

TEST(SubmitInventoryTests, SubmitInventoryStateTest) {
	mtesting::TestEventLoop loop;

	http::ClientConfig client_config;
	http::Client client {client_config, loop};

	conf::MenderConfig config;
	config.inventory_poll_interval_seconds = 1;
	context::MenderContext main_context {config};
	auto err = main_context.Initialize();

	Context ctx {main_context, loop};

	int n_submissions = 0;
	class MockInventoryClient : public inventory::InventoryAPI {
	public:
		MockInventoryClient(int &recorder, conf::MenderConfig &config) :
			recorder_ {recorder},
			config_ {config} {};

		error::Error PushData(
			const string &inventory_generators_dir,
			events::EventLoop &loop,
			api::Client &client,
			inventory::APIResponseHandler api_handler) override {
			recorder_++;
			api_handler(inventory::APIResponse {nullopt, nullopt, error::NoError});
			EXPECT_EQ(inventory_generators_dir, config_.paths.GetInventoryScriptsDir());
			if (recorder_ == 2) {
				loop.Stop();
			}
			return error::NoError;
		};

		void ClearDataCache() override {
		}

	private:
		int &recorder_;
		conf::MenderConfig &config_;
	} mock_inventory_client {n_submissions, config};

	ctx.inventory_client = shared_ptr<inventory::InventoryAPI>(
		&mock_inventory_client, [](inventory::InventoryAPI *cl) {});

	StateMachine state_machine {ctx, loop};
	err = state_machine.Run();
	ASSERT_EQ(err, error::NoError);

	EXPECT_EQ(n_submissions, 2);
}

TEST(StateTest, UpdateControlCleanup) {
	mtesting::TemporaryDirectory tmpdir;
	conf::MenderConfig config {};
	config.paths.SetDataStore(tmpdir.Path());

	context::MenderContext main_context {config};
	auto err = main_context.Initialize();
	ASSERT_EQ(err, error::NoError);

	mtesting::TestEventLoop event_loop;
	Context ctx {main_context, event_loop};

	auto &db = main_context.GetMenderStoreDB();
	err = db.Write(
		main_context.update_control_maps, common::ByteVectorFromString(R"({"some":"data"})"));
	ASSERT_EQ(err, error::NoError);

	auto exp_data = db.Read(main_context.update_control_maps);
	ASSERT_TRUE(exp_data);

	StateMachine state_machine {ctx, event_loop};
	state_machine.LoadStateFromDb();

	exp_data = db.Read(main_context.update_control_maps);
	ASSERT_FALSE(exp_data);
	EXPECT_EQ(exp_data.error().code, kvdb::MakeError(kvdb::KeyError, "").code);
}


TEST(DBSchemaMigrationTest, TestFromVersion1To2) {
	// Setup
	mtesting::TemporaryDirectory tmpdir;
	conf::MenderConfig config {};
	config.paths.SetDataStore(tmpdir.Path());

	context::MenderContext main_context {config};
	auto err = main_context.Initialize();
	ASSERT_EQ(err, error::NoError);

	mtesting::TestEventLoop event_loop;
	Context ctx {main_context, event_loop};

	auto &db = main_context.GetMenderStoreDB();

	err = db.WriteTransaction([&main_context](kv_db::Transaction &txn) -> error::Error {
		return txn.Write(main_context.state_data_key, common::ByteVectorFromString(R"({
  "Version": 1,
  "Name": "update-status-report",
  "UpdateInfo": {
    "Artifact": {
      "Source": {
        "URI": "https://s3.docker.mender.io:9000/mender-artifact-storage/c58c5f0e-6bb0-4905-a31c-8c0c0096f89b?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20231106%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20231106T085837Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3B%20filename%3D%22mender-98415760.mender%22&response-content-type=application%2Fvnd.mender-artifact&x-id=GetObject&X-Amz-Signature=eb326892ece383484c229f2a1dff50e9c4bd6c9f016a3db2aeebf76b67e4e4f3",
        "Expire": "2023-11-07T08:58:37.023637656Z"
      },
      "device_types_compatible": [
        "qemux86-64"
      ],
      "artifact_name": "mender-98415760"
    },
    "ID": "b35b0750-e2b7-4e24-91c6-6a162242aefc"
  },
  "UpdateStatus": ""
})"));
	});
	ASSERT_EQ(err, error::NoError);

	// END Setup

	// Load the data anew, and make sure the data is migrated
	StateData migrated_data {};
	auto exp_bool = ctx.LoadDeploymentStateData(migrated_data);
	ASSERT_TRUE(exp_bool) << exp_bool.error().String();
	ASSERT_TRUE(exp_bool.value());

	// Check the loaded migrated data
	EXPECT_EQ(migrated_data.version, kStateDataVersion);
	EXPECT_EQ(migrated_data.state, "update-status-report");
	ASSERT_EQ(migrated_data.update_info.artifact.payload_types.size(), 1);
	EXPECT_EQ(migrated_data.update_info.artifact.payload_types[0], "rootfs-image");
	EXPECT_EQ(migrated_data.update_info.reboot_requested.size(), 1);
	ASSERT_EQ(migrated_data.update_info.reboot_requested[0], "reboot-type-custom");
	EXPECT_EQ(migrated_data.update_info.supports_rollback, "rollback-supported");
	EXPECT_EQ(migrated_data.update_info.id, "b35b0750-e2b7-4e24-91c6-6a162242aefc");
	ASSERT_EQ(migrated_data.update_info.artifact.compatible_devices.size(), 1);
	EXPECT_EQ(migrated_data.update_info.artifact.compatible_devices[0], "qemux86-64");
	EXPECT_EQ(migrated_data.update_info.artifact.artifact_name, "mender-98415760");
}

class SubmitInventoryStateTests : public StateTests {
public:
	SubmitInventoryStateTests() :
		state_(3600, 0) {
	}

protected:
	void callPushDataHandler(inventory::APIResponse response) {
		state_.PushDataHandler(*ctx_, poster_, response);
		ctx_->deployment_timer.Cancel();
	}

	SubmitInventoryState state_;
};

TEST_F(SubmitInventoryStateTests, TooManyRequests_SecondsInRetryAfterHeader) {
	http::Transaction::HeaderMap headers;
	headers.insert({"Retry-After", "3600"});

	inventory::APIResponse resp = {
		http::StatusTooManyRequests,
		headers,
		MakeError(inventory::TooManyRequestsError, "Too many requests")};

	inventory::APIResponse resp_unauthorized = {
		http::StatusUnauthorized,
		headers,
		MakeError(inventory::BadResponseError, "doesn't matter")};

	EXPECT_CALL(poster_, PostEvent(StateEvent::Failure)).Times(6);
	testing::internal::CaptureStderr();

	callPushDataHandler(resp_unauthorized);
	callPushDataHandler(resp);
	callPushDataHandler(resp_unauthorized);
	callPushDataHandler(resp_unauthorized);
	callPushDataHandler(resp_unauthorized);
	callPushDataHandler(resp);

	auto output = testing::internal::GetCapturedStderr();
	// make sure that retrying with Retry-After does not modify normal
	// exponential backoff behavior
	ExpectLogsInOrder(
		output,
		{
			"Retrying inventory polling in 60 second",
			"Retrying inventory polling in 3600 second",
			"Retrying inventory polling in 60 second",
			"Retrying inventory polling in 60 second",
			"Retrying inventory polling in 120 second",
			"Retrying inventory polling in 3600 second",
		});
}

TEST_F(SubmitInventoryStateTests, TooManyRequests_DateInRetryAfterHeader) {
	time_t now = time(nullptr);
	time_t futureTime = now + 1298;
	struct tm *tm_ptr = gmtime(&futureTime);
	char buffer[100];
	// HTTP Format, e.g. "Wed, 21 Oct 2015 07:28:00 GMT", 1298 seconds from now
	strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", tm_ptr);
	string httpDate(buffer);

	http::Transaction::HeaderMap headers;
	headers.insert({"Retry-After", httpDate});

	inventory::APIResponse resp = {
		http::StatusTooManyRequests,
		headers,
		MakeError(mender::update::deployments::TooManyRequestsError, "Too many requests")};

	EXPECT_CALL(poster_, PostEvent(StateEvent::Failure)).Times(1);
	testing::internal::CaptureStderr();

	callPushDataHandler(resp);

	auto output = testing::internal::GetCapturedStderr();
	// make sure that retrying with Retry-After does not modify normal
	// exponential backoff behavior
	ExpectLogsInOrder(
		output,
		{
			"Retrying inventory polling in 1298 second",
		});
}

TEST_F(SubmitInventoryStateTests, TooManyRequests_NoRetryAfterHeader) {
	http::Transaction::HeaderMap headers;

	inventory::APIResponse resp = {
		http::StatusTooManyRequests,
		headers,
		MakeError(mender::update::deployments::TooManyRequestsError, "Too many requests")};

	int iterations = 7;
	EXPECT_CALL(poster_, PostEvent(StateEvent::Failure)).Times(iterations);
	testing::internal::CaptureStderr();

	for (int i = 0; i < iterations; i++) {
		callPushDataHandler(resp);
	}

	auto output = testing::internal::GetCapturedStderr();

	// Make sure that normal exponential backoff is used
	ExpectLogsInOrder(
		output,
		{
			"Retrying inventory polling in 60 second",
			"Retrying inventory polling in 60 second",
			"Retrying inventory polling in 60 second",
			"Retrying inventory polling in 120 second",
			"Retrying inventory polling in 120 second",
			"Retrying inventory polling in 120 second",
			"Retrying inventory polling in 240 second",
		});
}

class SendStatusUpdateStateTests : public StateTests {
public:
	SendStatusUpdateStateTests() :
		state_(nullopt, event_loop_, 3600, 0) {
	}

protected:
	void callDoStatusUpdateHandler(deployments::APIResponseError error) {
		state_.DoStatusUpdateHandler(*ctx_, poster_, error);
		ASSERT_TRUE(state_.retry_.has_value());
		state_.retry_.value().wait_timer.Cancel();
	}

	SendStatusUpdateState state_;
};

TEST_F(SendStatusUpdateStateTests, TooManyRequests_SecondsInRetryAfterHeader) {
	http::Transaction::HeaderMap headers;
	headers.insert({"Retry-After", "3600"});

	deployments::APIResponseError resp = {
		http::StatusTooManyRequests,
		headers,
		MakeError(inventory::TooManyRequestsError, "Too many requests")};

	deployments::APIResponseError resp_unauthorized = {
		http::StatusUnauthorized,
		headers,
		MakeError(inventory::BadResponseError, "doesn't matter")};

	testing::internal::CaptureStderr();

	callDoStatusUpdateHandler(resp_unauthorized);
	callDoStatusUpdateHandler(resp);
	callDoStatusUpdateHandler(resp_unauthorized);
	callDoStatusUpdateHandler(resp_unauthorized);
	callDoStatusUpdateHandler(resp_unauthorized);
	callDoStatusUpdateHandler(resp);

	auto output = testing::internal::GetCapturedStderr();
	// make sure that retrying with Retry-After does not modify normal
	// exponential backoff behavior
	ExpectLogsInOrder(
		output,
		{
			"Retrying status update after 60 second",
			"Retrying status update after 3600 second",
			"Retrying status update after 60 second",
			"Retrying status update after 60 second",
			"Retrying status update after 120 second",
			"Retrying status update after 3600 second",
		});
}

TEST_F(SendStatusUpdateStateTests, TooManyRequests_DateInRetryAfterHeader) {
	time_t now = time(nullptr);
	time_t futureTime = now + 1298;
	struct tm *tm_ptr = gmtime(&futureTime);
	char buffer[100];
	// HTTP Format, e.g. "Wed, 21 Oct 2015 07:28:00 GMT", 1298 seconds from now
	strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", tm_ptr);
	string httpDate(buffer);

	http::Transaction::HeaderMap headers;
	headers.insert({"Retry-After", httpDate});

	deployments::APIResponseError resp = {
		http::StatusTooManyRequests,
		headers,
		MakeError(mender::update::deployments::TooManyRequestsError, "Too many requests")};

	testing::internal::CaptureStderr();

	callDoStatusUpdateHandler(resp);

	auto output = testing::internal::GetCapturedStderr();
	// make sure that retrying with Retry-After does not modify normal
	// exponential backoff behavior
	ExpectLogsInOrder(
		output,
		{
			"Retrying status update after 1298 second",
		});
}

TEST_F(SendStatusUpdateStateTests, TooManyRequests_NoRetryAfterHeader) {
	http::Transaction::HeaderMap headers;

	deployments::APIResponseError resp = {
		http::StatusTooManyRequests,
		headers,
		MakeError(mender::update::deployments::TooManyRequestsError, "Too many requests")};

	int iterations = 7;
	testing::internal::CaptureStderr();

	for (int i = 0; i < iterations; i++) {
		callDoStatusUpdateHandler(resp);
	}

	auto output = testing::internal::GetCapturedStderr();

	// Make sure that normal exponential backoff is used
	ExpectLogsInOrder(
		output,
		{
			"Retrying status update after 60 second",
			"Retrying status update after 60 second",
			"Retrying status update after 60 second",
			"Retrying status update after 120 second",
			"Retrying status update after 120 second",
			"Retrying status update after 120 second",
			"Retrying status update after 240 second",
		});
}


} // namespace daemon
} // namespace update
} // namespace mender
