ores.cpp.shell-command.operation_tests

Table of Contents

The tests for one operation model's shell command unit.

Three cases per declared command, and they are the same three whatever the operation is because every command is built the same way: a signed-out session is refused, a wrong number of positionals is refused and names the count it expected, and a valid token vector gets as far as the transport. A test that reached the server would need a connection; Not connected to NATS is the proof that everything ahead of it, the guards and the request the tokens built, was satisfied.

The command count is asserted from the projection, so a message the model adds appears here without an edit to the test.

1. Template

The full template source. Edit here and re-tangle with compass build --direct tangle_codegen_templates to regenerate library/templates/cpp_shell_operation_tests.cpp.mustache.

{{! GENERATED FILE — tangled from projects/ores.codegen/library/templates/ores.cpp.shell-command.operation_tests.org. Edit the org source. }}
{{{cpp_license}}}
{{#operation}}
#include "ores.logging/make_logger.hpp"
#include "ores.nats/service/nats_client.hpp"
#include "ores.shell/app/command_feedback.hpp"
#include "ores.shell/app/commands/{{component}}/{{entity_singular}}_operations_commands.hpp"
#include <catch2/catch_test_macros.hpp>
#include <cli/cli.h>
#include <algorithm>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>

using ores::nats::service::nats_client;
using ores::shell::app::command_feedback;
using ores::shell::app::commands::{{entity_singular}}_operations_commands;
using namespace ores::logging;

namespace {

const std::string_view test_suite("ores.shell.{{component}}.tests");
const std::string tags("[commands]");

// One token per positional argument the command takes, in declaration order.
std::vector<std::string> tokens(const std::size_t count) {
    return std::vector<std::string>(count, std::string{"sample"});
}

}

TEST_CASE("{{entity_singular}}_operations_registers_every_declared_command", tags) {
    auto lg(make_logger(test_suite));

    cli::Menu root_menu("root");
    nats_client session;

    {{entity_singular}}_operations_commands::register_commands(root_menu, session);

    // The menu's completion list is the only public view of its children, so a
    // command that is missing from it was never registered.
    const auto completions = root_menu.GetCompletions("{{entity_singular}} ");
    for (const auto& verb : {
{{#shell_commands}}
             std::string{"{{entity_singular}} {{command}}"},
{{/shell_commands}}
         })
        CHECK(std::find(completions.begin(), completions.end(), verb) !=
              completions.end());

    BOOST_LOG_SEV(lg, debug) << "Registered {{shell_command_count}} command(s).";
}
{{#shell_commands}}
{{^public}}

TEST_CASE("{{entity_singular}}_operations_process_{{identifier}}_requires_a_session", tags) {
    auto lg(make_logger(test_suite));

    nats_client session;
    std::ostringstream out;

    command_feedback::reset();
    {{entity_singular}}_operations_commands::process_{{identifier}}(
        out, session, tokens({{positional_count}}));

    BOOST_LOG_SEV(lg, debug) << "Output for a signed-out session: " << out.str();
    CHECK(out.str().find("You must be logged in") != std::string::npos);
    CHECK(command_feedback::failed());
}
{{/public}}
{{#positional_count}}

TEST_CASE("{{entity_singular}}_operations_process_{{identifier}}_reports_the_expected_count",
          tags) {
    auto lg(make_logger(test_suite));

    nats_client session;
    nats_client::login_info info;
    info.username = "tester";
    info.jwt = "token";
    session.set_auth(std::move(info));
    std::ostringstream out;

    command_feedback::reset();
    {{entity_singular}}_operations_commands::process_{{identifier}}(out, session, {});

    BOOST_LOG_SEV(lg, debug) << "Output for an empty argument list: " << out.str();
    CHECK(out.str().find("Expected {{positional_count}} arguments, got 0.") !=
          std::string::npos);
    CHECK(command_feedback::failed());
}
{{/positional_count}}

TEST_CASE("{{entity_singular}}_operations_process_{{identifier}}_reaches_the_transport", tags) {
    auto lg(make_logger(test_suite));

    nats_client session;
    nats_client::login_info info;
    info.username = "tester";
    info.jwt = "token";
    session.set_auth(std::move(info));
    std::ostringstream out;

    command_feedback::reset();
    {{entity_singular}}_operations_commands::process_{{identifier}}(
        out, session, tokens({{positional_count}}));

    BOOST_LOG_SEV(lg, debug) << "Output for a valid token vector: " << out.str();
    // Whether the command carries a token or not, everything ahead of the
    // transport is satisfied, which is what the absence of a connection proves.
    CHECK(out.str().find("Not connected to NATS") != std::string::npos);
    CHECK(command_feedback::failed());
}
{{/shell_commands}}
{{/operation}}

2. See also

Emacs 29.3 (Org mode 9.6.15)