diff --git a/packages/cpp/ArmoniK.Api.Client/header/tasks/TasksClient.h b/packages/cpp/ArmoniK.Api.Client/header/tasks/TasksClient.h new file mode 100644 index 000000000..f9325996c --- /dev/null +++ b/packages/cpp/ArmoniK.Api.Client/header/tasks/TasksClient.h @@ -0,0 +1,104 @@ +#pragma once + +#include "tasks_common.pb.h" +#include "tasks_service.grpc.pb.h" + +#include "objects/Task.h" + +namespace armonik { +namespace api { +namespace client { + +class TasksClient { +public: + explicit TasksClient(std::unique_ptr stub) + : stub(std::move(stub)){}; + + /** + * List the Tasks + * @note This function returns a summary view of each task + * @param filters Filter to be used + * @param total Output for the total of session available for this request (used for pagination) + * @param page Page to request, use -1 to get all pages. + * @param page_size Size of the requested page, ignored if page is -1 + * @param sort How the tasks are sorted, ascending creation date by default + * @return List of tasks summary + * + * @note If the tasks corresponding to the filters change while this call is going for page==-1, + * or between calls, then the returned values may not be consistent depending on the sorting used. + * For example, a sort by ascending creation date (the default) will be stable if tasks are being created in + * between requests. + */ + std::vector + list_tasks(armonik::api::grpc::v1::tasks::Filters filters, int32_t &total, int32_t page = -1, int32_t page_size = 500, + armonik::api::grpc::v1::tasks::ListTasksRequest::Sort sort = default_sort()); + + /** + * List the Tasks + * @note This function returns a detailed view of each task + * @param filters Filter to be used + * @param total Output for the total of session available for this request (used for pagination) + * @param page Page to request, use -1 to get all pages. + * @param page_size Size of the requested page, ignored if page is -1 + * @param sort How the tasks are sorted, ascending creation date by default + * @return List of tasks summary + * + * @note If the tasks corresponding to the filters change while this call is going for page==-1, + * or between calls, then the returned values may not be consistent depending on the sorting used. + * For example, a sort by ascending creation date (the default) will be stable if tasks are being created in + * between requests. + */ + std::vector + list_tasks_detailed(armonik::api::grpc::v1::tasks::Filters filters, int32_t &total, int32_t page = -1, + int32_t page_size = 500, + armonik::api::grpc::v1::tasks::ListTasksRequest::Sort sort = default_sort()); + + /** + * Get informations about the given task + * @param session_id Task id + * @return TaskDetailed object containing information about the task + */ + armonik::api::grpc::v1::tasks::TaskDetailed get_task(std::string task_id); + + /** + * Cancel a list of tasks + * @param task_ids List of task ids + * @return Vector of TaskSummary objects containing information about the canceled tasks + */ + std::vector cancel_tasks(const std::vector &task_ids); + + /** + * Get the result ids of each task + * @param task_ids List of tasks + * @return Map associating the task id to its result ids + */ + std::map> get_result_ids(const std::vector &task_ids); + + /** + * Count tasks by status + * @param filters Task filter, optional + * @return Map of each task status and its count + */ + std::map + count_tasks_by_status(armonik::api::grpc::v1::tasks::Filters filters); + + /** + * Create tasks metadata and submit task for processing + * @param session_id Session id + * @param task_creations List of task creations + * @param task_options Task options common for this submission. Will be merged with the session task options + * @return List of submitted task info + */ + std::vector + submit_tasks(std::string session_id, const std::vector &task_creations, + const armonik::api::grpc::v1::TaskOptions &task_options = no_task_options); + +private: + std::unique_ptr stub; + static armonik::api::grpc::v1::tasks::ListTasksRequest::Sort default_sort(); + static const armonik::api::grpc::v1::TaskOptions no_task_options; +}; + +} // namespace client +} // namespace api +} // namespace armonik diff --git a/packages/cpp/ArmoniK.Api.Client/source/tasks/TasksClient.cpp b/packages/cpp/ArmoniK.Api.Client/source/tasks/TasksClient.cpp new file mode 100644 index 000000000..9c18bb1a0 --- /dev/null +++ b/packages/cpp/ArmoniK.Api.Client/source/tasks/TasksClient.cpp @@ -0,0 +1,231 @@ +#include "tasks/TasksClient.h" +#include "exceptions/ArmoniKApiException.h" +#include + +using namespace armonik::api::client; + +static inline ::grpc::Status call_stub_list(armonik::api::grpc::v1::tasks::Tasks::StubInterface *stub, + const armonik::api::grpc::v1::tasks::ListTasksRequest &request, + armonik::api::grpc::v1::tasks::ListTasksDetailedResponse *response) { + ::grpc::ClientContext context; + return stub->ListTasksDetailed(&context, request, response); +} + +static inline ::grpc::Status call_stub_list(armonik::api::grpc::v1::tasks::Tasks::StubInterface *stub, + const armonik::api::grpc::v1::tasks::ListTasksRequest &request, + armonik::api::grpc::v1::tasks::ListTasksResponse *response) { + ::grpc::ClientContext context; + return stub->ListTasks(&context, request, response); +} + +/** + * Common function called to list tasks + * @tparam T Result value type (TaskSummary or TaskDetailed + * @tparam U Response type + * @param stub Task stub + * @param filters Filter to be used + * @param total Output for the total of session available for this request (used for pagination) + * @param page Page to request, use -1 to get all pages. + * @param page_size Size of the requested page, ignored if page is -1 + * @param sort How the tasks are sorted, ascending creation date by default + * @return Vector of information about the tasks + */ +template ().tasks()), + class = decltype(std::declval().total())> +static std::vector list_tasks_common(armonik::api::grpc::v1::tasks::Tasks::StubInterface *stub, + armonik::api::grpc::v1::tasks::Filters filters, int32_t &total, int32_t page, + int32_t page_size, armonik::api::grpc::v1::tasks::ListTasksRequest::Sort sort) { + armonik::api::grpc::v1::tasks::ListTasksRequest request; + U response; + *request.mutable_filters() = std::move(filters); + *request.mutable_sort() = std::move(sort); + request.set_page_size(page_size); + + if (page >= 0) { + request.set_page(page); + auto status = call_stub_list(stub, request, &response); + + if (!status.ok()) { + throw armonik::api::common::exceptions::ArmoniKApiException("Unable to list results " + status.error_message()); + } + total = response.total(); + return {response.tasks().begin(), response.tasks().end()}; + } else { + std::vector rawResults; + int current_page = 0; + do { + request.set_page(current_page); + ::grpc::ClientContext context; + auto status = call_stub_list(stub, request, &response); + if (!status.ok()) { + throw armonik::api::common::exceptions::ArmoniKApiException("Unable to list results " + status.error_message()); + } + // Append only the additional tasks + // If the current_page is a re-request, this will add only the new information + rawResults.insert(rawResults.end(), + response.tasks().begin() + ((int32_t)rawResults.size() - current_page * page_size), + response.tasks().end()); + if (response.total() >= page_size) { + ++current_page; + } + + response.clear_tasks(); + } while ((int32_t)rawResults.size() < response.total()); + total = response.total(); + return rawResults; + } +} + +std::vector +TasksClient::list_tasks(armonik::api::grpc::v1::tasks::Filters filters, int32_t &total, int32_t page, int32_t page_size, + armonik::api::grpc::v1::tasks::ListTasksRequest::Sort sort) { + return list_tasks_common(stub.get(), std::move(filters), total, + page, page_size, std::move(sort)); +} + +std::vector +TasksClient::list_tasks_detailed(armonik::api::grpc::v1::tasks::Filters filters, int32_t &total, int32_t page, + int32_t page_size, armonik::api::grpc::v1::tasks::ListTasksRequest::Sort sort) { + return list_tasks_common( + stub.get(), std::move(filters), total, page, page_size, std::move(sort)); +} + +armonik::api::grpc::v1::tasks::TaskDetailed TasksClient::get_task(std::string task_id) { + ::grpc::ClientContext context; + armonik::api::grpc::v1::tasks::GetTaskRequest request; + armonik::api::grpc::v1::tasks::GetTaskResponse response; + *request.mutable_task_id() = std::move(task_id); + auto status = stub->GetTask(&context, request, &response); + if (!status.ok()) { + std::stringstream message; + message << "Error getting task: " << status.error_code() << ": " << status.error_message() + << ". details : " << status.error_details() << std::endl; + auto str = message.str(); + throw armonik::api::common::exceptions::ArmoniKApiException(str); + } + + return response.task(); +} + +std::vector +TasksClient::cancel_tasks(const std::vector &task_ids) { + ::grpc::ClientContext context; + armonik::api::grpc::v1::tasks::CancelTasksRequest request; + armonik::api::grpc::v1::tasks::CancelTasksResponse response; + request.mutable_task_ids()->Add(task_ids.begin(), task_ids.end()); + auto status = stub->CancelTasks(&context, request, &response); + if (!status.ok()) { + std::stringstream message; + message << "Error canceling tasks: " << status.error_code() << ": " << status.error_message() + << ". details : " << status.error_details() << std::endl; + auto str = message.str(); + throw armonik::api::common::exceptions::ArmoniKApiException(str); + } + + return {std::make_move_iterator(response.mutable_tasks()->begin()), + std::make_move_iterator(response.mutable_tasks()->end())}; +} + +std::map> TasksClient::get_result_ids(const std::vector &task_ids) { + ::grpc::ClientContext context; + armonik::api::grpc::v1::tasks::GetResultIdsRequest request; + armonik::api::grpc::v1::tasks::GetResultIdsResponse response; + request.mutable_task_id()->Add(task_ids.begin(), task_ids.end()); + auto status = stub->GetResultIds(&context, request, &response); + if (!status.ok()) { + std::stringstream message; + message << "Error getting result ids from tasks: " << status.error_code() << ": " << status.error_message() + << ". details : " << status.error_details() << std::endl; + auto str = message.str(); + throw armonik::api::common::exceptions::ArmoniKApiException(str); + } + + std::map> map_results; + + for (auto &&tid_rid : *response.mutable_task_results()) { + map_results[std::move(*tid_rid.mutable_task_id())] = { + std::make_move_iterator(tid_rid.mutable_result_ids()->begin()), + std::make_move_iterator(tid_rid.mutable_result_ids()->end())}; + } + return map_results; +} + +std::map +TasksClient::count_tasks_by_status(armonik::api::grpc::v1::tasks::Filters filters) { + ::grpc::ClientContext context; + armonik::api::grpc::v1::tasks::CountTasksByStatusRequest request; + armonik::api::grpc::v1::tasks::CountTasksByStatusResponse response; + *request.mutable_filters() = std::move(filters); + + auto status = stub->CountTasksByStatus(&context, request, &response); + if (!status.ok()) { + std::stringstream message; + message << "Error getting result ids from tasks: " << status.error_code() << ": " << status.error_message() + << ". details : " << status.error_details() << std::endl; + auto str = message.str(); + throw armonik::api::common::exceptions::ArmoniKApiException(str); + } + + std::map map_status; + for (auto &&status_count : *response.mutable_status()) { + map_status[status_count.status()] = status_count.count(); + } + return map_status; +} + +std::vector +TasksClient::submit_tasks(std::string session_id, const std::vector &task_creations, + const armonik::api::grpc::v1::TaskOptions &task_options) { + ::grpc::ClientContext context; + armonik::api::grpc::v1::tasks::SubmitTasksRequest request; + armonik::api::grpc::v1::tasks::SubmitTasksResponse response; + + *request.mutable_session_id() = std::move(session_id); + if (task_options.max_retries() != INT32_MIN) { + // not default task_options + *request.mutable_task_options() = task_options; + } + + for (auto &&t : task_creations) { + auto new_t = request.mutable_task_creations()->Add(); + *new_t->mutable_payload_id() = t.payload_id; + new_t->mutable_data_dependencies()->Add(t.data_dependencies.begin(), t.data_dependencies.end()); + new_t->mutable_expected_output_keys()->Add(t.expected_output_keys.begin(), t.expected_output_keys.end()); + if (t.taskOptions.max_retries() != INT32_MIN) { + // not default task_options + *new_t->mutable_task_options() = t.taskOptions; + } + } + + auto status = stub->SubmitTasks(&context, request, &response); + if (!status.ok()) { + std::stringstream message; + message << "Error submitting tasks " << status.error_code() << ": " << status.error_message() + << ". details : " << status.error_details() << std::endl; + auto str = message.str(); + throw armonik::api::common::exceptions::ArmoniKApiException(str); + } + std::vector infos; + infos.reserve(response.task_infos_size()); + for (auto &&info : *response.mutable_task_infos()) { + infos.push_back({std::move(*info.mutable_task_id()), + std::vector{std::make_move_iterator(info.mutable_expected_output_ids()->begin()), + std::make_move_iterator(info.mutable_expected_output_ids()->end())}, + std::vector{std::make_move_iterator(info.mutable_data_dependencies()->begin()), + std::make_move_iterator(info.mutable_data_dependencies()->end())}, + std::move(*info.mutable_payload_id())}); + } + return infos; +} + +armonik::api::grpc::v1::tasks::ListTasksRequest::Sort TasksClient::default_sort() { + armonik::api::grpc::v1::tasks::ListTasksRequest::Sort sort; + sort.set_direction(grpc::v1::sort_direction::SORT_DIRECTION_ASC); + sort.mutable_field()->mutable_task_summary_field()->set_field(grpc::v1::tasks::TASK_SUMMARY_ENUM_FIELD_CREATED_AT); + return sort; +} + +const armonik::api::grpc::v1::TaskOptions TasksClient::no_task_options = + armonik::api::common::TaskCreation::get_no_task_options(); diff --git a/packages/cpp/ArmoniK.Api.Common/header/objects/Task.h b/packages/cpp/ArmoniK.Api.Common/header/objects/Task.h new file mode 100644 index 000000000..08ff3f252 --- /dev/null +++ b/packages/cpp/ArmoniK.Api.Common/header/objects/Task.h @@ -0,0 +1,64 @@ +#pragma once + +#include "tasks_common.pb.h" +#include +#include + +namespace armonik { +namespace api { +namespace common { +struct TaskCreation { + /** + * Payload Id + */ + std::string payload_id; + /** + * Expected output keys + */ + std::vector expected_output_keys; + + /** + * Data dependencies, none by default + */ + std::vector data_dependencies = {}; + + /** + * Per task task options, none by default + */ + armonik::api::grpc::v1::TaskOptions taskOptions = get_no_task_options(); + + /** + * Default "no task option" value + * @return A task option to use to ignore the task options parameter + */ + static armonik::api::grpc::v1::TaskOptions get_no_task_options() { + armonik::api::grpc::v1::TaskOptions options; + options.set_max_retries(INT32_MIN); + return options; + } +}; + +struct TaskInfo { + /** + * Id of the task + */ + std::string task_id; + + /** + * Expected output result ids + */ + std::vector expected_output_ids; + + /** + * Data dependencies of the task + */ + std::vector data_dependencies; + + /** + * Id of the payload + */ + std::string payload_id; +}; +} // namespace common +} // namespace api +} // namespace armonik diff --git a/packages/cpp/ArmoniK.Api.Tests/source/TasksClientTest.cpp b/packages/cpp/ArmoniK.Api.Tests/source/TasksClientTest.cpp new file mode 100644 index 000000000..bd8553e4b --- /dev/null +++ b/packages/cpp/ArmoniK.Api.Tests/source/TasksClientTest.cpp @@ -0,0 +1,226 @@ +#include +#include + +#include "common.h" +#include "logger/formatter.h" +#include "logger/logger.h" +#include "logger/writer.h" + +#include "objects/Task.h" +#include "results/ResultsClient.h" +#include "sessions/SessionsClient.h" +#include "tasks/TasksClient.h" +#include "tasks_service.grpc.pb.h" + +using Logger = armonik::api::common::logger::Logger; + +armonik::api::grpc::v1::tasks::Filters get_session_id_filter(std::string session_id) { + armonik::api::grpc::v1::tasks::Filters filters; + armonik::api::grpc::v1::tasks::FilterField filter_field; + filter_field.mutable_field()->mutable_task_summary_field()->set_field( + armonik::api::grpc::v1::tasks::TASK_SUMMARY_ENUM_FIELD_SESSION_ID); + *filter_field.mutable_filter_string()->mutable_value() = std::move(session_id); + filter_field.mutable_filter_string()->set_operator_(armonik::api::grpc::v1::FILTER_STRING_OPERATOR_EQUAL); + *filters.mutable_or_()->Add()->mutable_and_()->Add() = filter_field; + return filters; +} + +TEST(Tasks, submit_tasks_test) { + Logger log{armonik::api::common::logger::writer_console(), armonik::api::common::logger::formatter_plain(true)}; + std::shared_ptr<::grpc::Channel> channel; + armonik::api::grpc::v1::TaskOptions task_options; + + init(channel, task_options, log); + auto task_options_submit = task_options; + task_options_submit.set_priority(task_options.priority() + 1); + auto task_options_unique = task_options; + task_options_unique.set_priority(task_options.priority() + 2); + + auto session_id = armonik::api::client::SessionsClient(armonik::api::grpc::v1::sessions::Sessions::NewStub(channel)) + .create_session(task_options); + auto result_client = armonik::api::client::ResultsClient(armonik::api::grpc::v1::results::Results::NewStub(channel)); + auto payload_id = result_client.create_results( + session_id, std::vector>{{"name", "payload"}})["name"]; + auto result_id = result_client.create_results_metadata(session_id, {"result"})["result"]; + + auto client = armonik::api::client::TasksClient(armonik::api::grpc::v1::tasks::Tasks::NewStub(channel)); + + std::vector tasks_simple; + ASSERT_NO_THROW(tasks_simple = + client.submit_tasks(session_id, {armonik::api::common::TaskCreation{payload_id, {{result_id}}}})); + ASSERT_EQ(tasks_simple.size(), 1); + + std::vector tasks_submit_override; + ASSERT_NO_THROW(tasks_submit_override = + client.submit_tasks(session_id, {armonik::api::common::TaskCreation{payload_id, {{result_id}}}}, + task_options_submit)); + ASSERT_EQ(tasks_submit_override.size(), 1); + + std::vector tasks_submit_unique_override; + ASSERT_NO_THROW(tasks_submit_unique_override = client.submit_tasks( + session_id, + {armonik::api::common::TaskCreation{payload_id, {{result_id}}, {}, task_options_unique}}, + task_options_submit)); + ASSERT_NO_THROW(tasks_submit_unique_override.push_back(client.submit_tasks( + session_id, {armonik::api::common::TaskCreation{payload_id, {{result_id}}, {}, task_options_unique}})[0])); + ASSERT_EQ(tasks_submit_unique_override.size(), 2); + + ASSERT_EQ(client.get_task(tasks_simple[0].task_id).options().priority(), task_options.priority()); + ASSERT_EQ(client.get_task(tasks_submit_override[0].task_id).options().priority(), task_options_submit.priority()); + ASSERT_EQ(client.get_task(tasks_submit_unique_override[0].task_id).options().priority(), + task_options_unique.priority()); + ASSERT_EQ(client.get_task(tasks_submit_unique_override[1].task_id).options().priority(), + task_options_unique.priority()); +} + +TEST(Tasks, count_tasks_test) { + Logger log{armonik::api::common::logger::writer_console(), armonik::api::common::logger::formatter_plain(true)}; + std::shared_ptr<::grpc::Channel> channel; + armonik::api::grpc::v1::TaskOptions task_options; + + init(channel, task_options, log); + + auto session_id = armonik::api::client::SessionsClient(armonik::api::grpc::v1::sessions::Sessions::NewStub(channel)) + .create_session(task_options); + auto result_client = armonik::api::client::ResultsClient(armonik::api::grpc::v1::results::Results::NewStub(channel)); + auto payload_id = result_client.create_results( + session_id, std::vector>{{"name", "payload"}})["name"]; + auto result_id = result_client.create_results_metadata(session_id, {"result"})["result"]; + auto client = armonik::api::client::TasksClient(armonik::api::grpc::v1::tasks::Tasks::NewStub(channel)); + + auto filters = get_session_id_filter(session_id); + + std::map status_count; + ASSERT_NO_THROW(status_count = client.count_tasks_by_status(filters)); + ASSERT_EQ(std::accumulate(status_count.begin(), status_count.end(), 0, + [](int a, std::pair p) { + return a + p.second; + }), + 0); + + client.submit_tasks(session_id, {armonik::api::common::TaskCreation{payload_id, {{result_id}}}}); + + ASSERT_NO_THROW(status_count = client.count_tasks_by_status(filters)); + ASSERT_EQ(std::accumulate(status_count.begin(), status_count.end(), 0, + [](int a, std::pair p) { + return a + p.second; + }), + 1); +} + +TEST(Tasks, get_result_ids_test) { + Logger log{armonik::api::common::logger::writer_console(), armonik::api::common::logger::formatter_plain(true)}; + std::shared_ptr<::grpc::Channel> channel; + armonik::api::grpc::v1::TaskOptions task_options; + + init(channel, task_options, log); + + auto session_id = armonik::api::client::SessionsClient(armonik::api::grpc::v1::sessions::Sessions::NewStub(channel)) + .create_session(task_options); + auto result_client = armonik::api::client::ResultsClient(armonik::api::grpc::v1::results::Results::NewStub(channel)); + auto payload_id = result_client.create_results( + session_id, std::vector>{{"name", "payload"}})["name"]; + auto result_id = result_client.create_results_metadata(session_id, {"result"})["result"]; + auto client = armonik::api::client::TasksClient(armonik::api::grpc::v1::tasks::Tasks::NewStub(channel)); + + auto task_id = + client.submit_tasks(session_id, {armonik::api::common::TaskCreation{payload_id, {result_id}}})[0].task_id; + + std::map> tid_rids; + ASSERT_NO_THROW(tid_rids = client.get_result_ids({task_id})); + ASSERT_EQ(tid_rids.at(task_id).size(), 1); + ASSERT_EQ(tid_rids.at(task_id).at(0), result_id); +} + +TEST(Tasks, get_task_test) { + Logger log{armonik::api::common::logger::writer_console(), armonik::api::common::logger::formatter_plain(true)}; + std::shared_ptr<::grpc::Channel> channel; + armonik::api::grpc::v1::TaskOptions task_options; + + init(channel, task_options, log); + + auto session_id = armonik::api::client::SessionsClient(armonik::api::grpc::v1::sessions::Sessions::NewStub(channel)) + .create_session(task_options); + auto result_client = armonik::api::client::ResultsClient(armonik::api::grpc::v1::results::Results::NewStub(channel)); + auto payload_id = result_client.create_results( + session_id, std::vector>{{"name", "payload"}})["name"]; + auto result_id = result_client.create_results_metadata(session_id, {"result"})["result"]; + auto client = armonik::api::client::TasksClient(armonik::api::grpc::v1::tasks::Tasks::NewStub(channel)); + + auto task_id = + client.submit_tasks(session_id, {armonik::api::common::TaskCreation{payload_id, {result_id}}})[0].task_id; + + armonik::api::grpc::v1::tasks::TaskDetailed details; + ASSERT_NO_THROW(details = client.get_task(task_id)); + ASSERT_EQ(details.id(), task_id); +} + +TEST(Tasks, cancel_tasks_test) { + GTEST_SKIP() << "Core bug #523"; + Logger log{armonik::api::common::logger::writer_console(), armonik::api::common::logger::formatter_plain(true)}; + std::shared_ptr<::grpc::Channel> channel; + armonik::api::grpc::v1::TaskOptions task_options; + + init(channel, task_options, log); + + auto session_id = armonik::api::client::SessionsClient(armonik::api::grpc::v1::sessions::Sessions::NewStub(channel)) + .create_session(task_options); + auto result_client = armonik::api::client::ResultsClient(armonik::api::grpc::v1::results::Results::NewStub(channel)); + auto payload_id = result_client.create_results( + session_id, std::vector>{{"name", "payload"}})["name"]; + auto result_id = result_client.create_results_metadata(session_id, {"result"})["result"]; + auto dd_id = result_client.create_results_metadata(session_id, {"DD"})["DD"]; + auto client = armonik::api::client::TasksClient(armonik::api::grpc::v1::tasks::Tasks::NewStub(channel)); + + auto task_id = + client.submit_tasks(session_id, {armonik::api::common::TaskCreation{payload_id, {result_id}, {dd_id}}})[0] + .task_id; + + ASSERT_NE(client.get_task(task_id).status(), armonik::api::grpc::v1::task_status::TASK_STATUS_CANCELLED); + + ASSERT_EQ(client.cancel_tasks({task_id}).at(0).status(), armonik::api::grpc::v1::task_status::TASK_STATUS_CANCELLED); +} + +TEST(Tasks, list_tasks_test) { + Logger log{armonik::api::common::logger::writer_console(), armonik::api::common::logger::formatter_plain(true)}; + std::shared_ptr<::grpc::Channel> channel; + armonik::api::grpc::v1::TaskOptions task_options; + + init(channel, task_options, log); + + auto session_id = armonik::api::client::SessionsClient(armonik::api::grpc::v1::sessions::Sessions::NewStub(channel)) + .create_session(task_options); + auto result_client = armonik::api::client::ResultsClient(armonik::api::grpc::v1::results::Results::NewStub(channel)); + auto payload_id = result_client.create_results( + session_id, std::vector>{{"name", "payload"}})["name"]; + auto result_id = result_client.create_results_metadata(session_id, {"result"})["result"]; + auto client = armonik::api::client::TasksClient(armonik::api::grpc::v1::tasks::Tasks::NewStub(channel)); + + client.submit_tasks(session_id, {{payload_id, {result_id}}}); + + int total; + ASSERT_EQ(client.list_tasks(get_session_id_filter(session_id), total).size(), 1); + ASSERT_EQ(total, 1); +} + +TEST(Tasks, list_tasks_detailed_test) { + Logger log{armonik::api::common::logger::writer_console(), armonik::api::common::logger::formatter_plain(true)}; + std::shared_ptr<::grpc::Channel> channel; + armonik::api::grpc::v1::TaskOptions task_options; + + init(channel, task_options, log); + + auto session_id = armonik::api::client::SessionsClient(armonik::api::grpc::v1::sessions::Sessions::NewStub(channel)) + .create_session(task_options); + auto result_client = armonik::api::client::ResultsClient(armonik::api::grpc::v1::results::Results::NewStub(channel)); + auto payload_id = result_client.create_results( + session_id, std::vector>{{"name", "payload"}})["name"]; + auto result_id = result_client.create_results_metadata(session_id, {"result"})["result"]; + auto client = armonik::api::client::TasksClient(armonik::api::grpc::v1::tasks::Tasks::NewStub(channel)); + + client.submit_tasks(session_id, {{payload_id, {result_id}}}); + + int total; + ASSERT_EQ(client.list_tasks_detailed(get_session_id_filter(session_id), total).size(), 1); + ASSERT_EQ(total, 1); +}