<commit_before>// Copyright 2014 Toggl Desktop developers.

#include <vector>

#include "gtest/gtest.h"

#include "./../lib/include/toggl_api.h"
#include "./../toggl_api_private.h"
#include "./test_data.h"
#include "./../settings.h"
#include "./../proxy.h"

#include "Poco/FileStream.h"
#include "Poco/File.h"
#include "Poco/Path.h"
#include "Poco/DateTime.h"
#include "Poco/LocalDateTime.h"

namespace toggl {

namespace testing {

namespace testresult {

// on_url
std::string url("");

// on_reminder
std::string reminder_title("");
std::string reminder_informative_text("");
std::string error("");

// on_online_state
bool online_state(false);

// on_login
uint64_t user_id(0);

// on_display_settings
_Bool open_settings(false);
Settings settings;
bool use_proxy(false);
Proxy proxy;

// on_update
std::string update_channel("");

// on_display_idle_notification
std::string idle_guid("");
std::string idle_since("");
std::string idle_duration("");
uint64_t idle_started(0);

// on_display_timer_state
TimeEntry timer_state;

// on_project_autocomplete
std::vector<std::string> projects;

// on_time_entry_list
std::vector<TimeEntry> time_entries;

TimeEntry time_entry_by_guid(const std::string guid) {
    TimeEntry te;
    for (std::size_t i = 0; i < testing::testresult::time_entries.size();
            i++) {
        if (testing::testresult::time_entries[i].GUID() == guid) {
            te = testing::testresult::time_entries[i];
            break;
        }
    }
    return te;
}

// on_time_entry_editor
TimeEntry editor_state;
_Bool editor_open(false);
std::string editor_focused_field_name("");

}  // namespace testresult

void on_app(const _Bool open) {
}

void on_error(
    const char *errmsg,
    const _Bool user_error) {
    if (errmsg) {
        testresult::error = std::string(errmsg);
        return;
    }
    testresult::error = std::string("");
}

void on_update(
    const _Bool open,
    TogglUpdateView *view) {
    testresult::update_channel = std::string(view->UpdateChannel);
}

void on_online_state(const _Bool is_online, const char *reason) {
    testresult::online_state = is_online;
}

void on_url(const char *url) {
    testresult::url = std::string(url);
}

void on_login(const _Bool open, const uint64_t user_id) {
    testresult::user_id = user_id;
}

void on_reminder(const char *title, const char *informative_text) {
    testresult::reminder_title = std::string(title);
    testresult::reminder_informative_text = std::string(informative_text);
}

void on_time_entry_list(
    const _Bool open,
    TogglTimeEntryView *first) {
    testing::testresult::time_entries.clear();
    TogglTimeEntryView *it = first;
    while (it) {
        TimeEntry te;
        te.SetGUID(it->GUID);
        te.SetDurationInSeconds(it->DurationInSeconds);
        te.SetDescription(it->Description);
        te.SetStart(it->Started);
        te.SetStop(it->Ended);
        testing::testresult::time_entries.push_back(te);
        it = reinterpret_cast<TogglTimeEntryView *>(it->Next);
    }
}

void on_time_entry_autocomplete(TogglAutocompleteView *first) {
}

void on_project_autocomplete(TogglAutocompleteView *first) {
    testing::testresult::projects.clear();
    TogglAutocompleteView *it = first;
    while (it) {
        testing::testresult::projects.push_back(
            std::string(it->ProjectLabel));
        it = reinterpret_cast<TogglAutocompleteView *>(it->Next);
    }
}

void on_client_select(TogglGenericView *first) {
}

void on_workspace_select(TogglGenericView *first) {
}

void on_tags(TogglGenericView *first) {
}

void on_time_entry_editor(
    const _Bool open,
    TogglTimeEntryView *te,
    const char *focused_field_name) {
    testing::testresult::editor_state = TimeEntry();
    testing::testresult::editor_state.SetGUID(te->GUID);
    testing::testresult::editor_open = open;
    testing::testresult::editor_focused_field_name =
        std::string(focused_field_name);
}

void on_display_settings(
    const _Bool open,
    TogglSettingsView *settings) {

    testing::testresult::open_settings = open;

    testing::testresult::settings.use_idle_detection =
        settings->UseIdleDetection;
    testing::testresult::settings.menubar_timer = settings->MenubarTimer;
    testing::testresult::settings.reminder = settings->Reminder;
    testing::testresult::settings.dock_icon = settings->DockIcon;
    testing::testresult::settings.on_top = settings->OnTop;

    testing::testresult::use_proxy = settings->UseProxy;

    testing::testresult::proxy.host = std::string(settings->ProxyHost);
    testing::testresult::proxy.port = settings->ProxyPort;
    testing::testresult::proxy.username = std::string(settings->ProxyUsername);
    testing::testresult::proxy.password = std::string(settings->ProxyPassword);
}

void on_display_timer_state(TogglTimeEntryView *te) {
    testing::testresult::timer_state = TimeEntry();
    if (te) {
        testing::testresult::timer_state.SetStart(te->Started);
        testing::testresult::timer_state.SetGUID(te->GUID);
        testing::testresult::timer_state.SetDurationInSeconds(
            te->DurationInSeconds);
        testing::testresult::timer_state.SetDescription(te->Description);
        if (te->Tags) {
            testing::testresult::timer_state.SetTags(te->Tags);
        }
        testing::testresult::timer_state.SetBillable(te->Billable);
    }
}

void on_display_idle_notification(
    const char *guid,
    const char *since,
    const char *duration,
    const uint64_t started) {
    testing::testresult::idle_since = std::string(since);
    testing::testresult::idle_started = started;
    testing::testresult::idle_duration = std::string(duration);
    testing::testresult::idle_guid = std::string(guid);
}

void on_apply_settings(
    TogglSettingsView *settings) {
    testing::testresult::use_proxy = settings->UseProxy;
    testing::testresult::settings.use_idle_detection =
        settings->UseIdleDetection;
    testing::testresult::settings.menubar_timer =
        settings->MenubarTimer;
    testing::testresult::settings.dock_icon = settings->DockIcon;
    testing::testresult::settings.on_top = settings->OnTop;
}

class App {
 public:
    App() {
        Poco::File f(TESTDB);
        if (f.exists()) {
            f.remove(false);
        }

        toggl_set_log_path("test.log");

        ctx_ = toggl_context_init("tests", "0.1");

        poco_assert(toggl_set_db_path(ctx_, TESTDB));

        Poco::Path path("src/ssl/cacert.pem");
        toggl_set_cacert_path(ctx_, path.toString().c_str());

        toggl_on_show_app(ctx_, on_app);
        toggl_on_error(ctx_, on_error);
        toggl_on_update(ctx_, on_update);
        toggl_on_online_state(ctx_, on_online_state);
        toggl_on_login(ctx_, on_login);
        toggl_on_url(ctx_, on_url);
        toggl_on_reminder(ctx_, on_reminder);
        toggl_on_time_entry_list(ctx_, on_time_entry_list);
        toggl_on_time_entry_autocomplete(ctx_, on_time_entry_autocomplete);
        toggl_on_project_autocomplete(ctx_, on_project_autocomplete);
        toggl_on_workspace_select(ctx_, on_workspace_select);
        toggl_on_client_select(ctx_, on_client_select);
        toggl_on_tags(ctx_, on_tags);
        toggl_on_time_entry_editor(ctx_, on_time_entry_editor);
        toggl_on_settings(ctx_, on_display_settings);
        toggl_on_timer_state(ctx_, on_display_timer_state);
        toggl_on_idle_notification(ctx_, on_display_idle_notification);

        poco_assert(toggl_ui_start(ctx_));
    }
    ~App() {
        toggl_context_clear(ctx_);
        ctx_ = 0;
    }

    void *ctx() {
        return ctx_;
    }

 private:
    void *ctx_;
};

}  // namespace testing

TEST(TogglApiTest, toggl_context_init) {
    testing::App app;
}

TEST(TogglApiTest, toggl_set_settings) {
    testing::App app;

    ASSERT_TRUE(toggl_set_settings(app.ctx(),
                                   false, false, false, false, false, 5));

    ASSERT_FALSE(testing::testresult::settings.use_idle_detection);
    ASSERT_FALSE(testing::testresult::settings.menubar_timer);
    ASSERT_FALSE(testing::testresult::settings.dock_icon);
    ASSERT_FALSE(testing::testresult::settings.on_top);
    ASSERT_FALSE(testing::testresult::settings.reminder);

    ASSERT_TRUE(toggl_set_settings(app.ctx(),
                                   true, true, true, true, true, 5));

    ASSERT_TRUE(testing::testresult::settings.use_idle_detection);
    ASSERT_TRUE(testing::testresult::settings.menubar_timer);
    ASSERT_TRUE(testing::testresult::settings.dock_icon);
    ASSERT_TRUE(testing::testresult::settings.on_top);
    ASSERT_TRUE(testing::testresult::settings.reminder);
}

TEST(TogglApiTest, toggl_set_proxy_settings) {
    testing::App app;

    ASSERT_TRUE(toggl_set_proxy_settings(
        app.ctx(), 1, "localhost", 8000, "johnsmith", "secret"));

    ASSERT_TRUE(testing::testresult::use_proxy);
    ASSERT_EQ(std::string("localhost"),
              std::string(testing::testresult::proxy.host));
    ASSERT_EQ(8000,
              static_cast<int>(testing::testresult::proxy.port));
    ASSERT_EQ(std::string("johnsmith"),
              std::string(testing::testresult::proxy.username));
    ASSERT_EQ(std::string("secret"),
              std::string(testing::testresult::proxy.password));
}

TEST(TogglApiTest, toggl_set_update_channel) {
    testing::App app;

    std::string default_channel("stable");

    toggl_about(app.ctx());

    ASSERT_EQ(default_channel, testing::testresult::update_channel);

    // Also check that the API itself thinks the default channel is
    char *str = toggl_get_update_channel(app.ctx());
    ASSERT_EQ(default_channel, std::string(str));
    free(str);

    ASSERT_FALSE(toggl_set_update_channel(app.ctx(), "invalid"));

    // The channel should be the same in the API
    str = toggl_get_update_channel(app.ctx());
    ASSERT_EQ(default_channel, std::string(str));
    free(str);

    ASSERT_TRUE(toggl_set_update_channel(app.ctx(), "beta"));

    ASSERT_EQ(std::string("beta"), testing::testresult::update_channel);

    // The channel should have been changed in the API
    str = toggl_get_update_channel(app.ctx());
    ASSERT_EQ(std::string("beta"), std::string(str));
    free(str);

    ASSERT_TRUE(toggl_set_update_channel(app.ctx(), "dev"));

    ASSERT_EQ(std::string("dev"), testing::testresult::update_channel);

    // The channel should have been changed in the API
    str = toggl_get_update_channel(app.ctx());
    ASSERT_EQ(std::string("dev"), std::string(str));
    free(str);

    ASSERT_TRUE(toggl_set_update_channel(app.ctx(), "stable"));

    ASSERT_EQ(std::string("stable"), testing::testresult::update_channel);

    // The channel should have been changed in the API
    str = toggl_get_update_channel(app.ctx());
    ASSERT_EQ(std::string("stable"), std::string(str));
    free(str);
}

TEST(TogglApiTest, toggl_set_log_level) {
    toggl_set_log_level("trace");
}

TEST(TogglApiTest, toggl_format_duration_in_seconds_hhmmss) {
    char *str = toggl_format_duration_in_seconds_hhmmss(10);
    ASSERT_EQ("00:00:10", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmmss(60);
    ASSERT_EQ("00:01:00", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmmss(65);
    ASSERT_EQ("00:01:05", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmmss(3600);
    ASSERT_EQ("01:00:00", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmmss(5400);
    ASSERT_EQ("01:30:00", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmmss(5410);
    ASSERT_EQ("01:30:10", std::string(str));
    free(str);
}

TEST(TogglApiTest, toggl_format_duration_in_seconds_hhmm) {
    char *str  = toggl_format_duration_in_seconds_hhmm(10);
    ASSERT_EQ("00:00", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmm(60);
    ASSERT_EQ("00:01", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmm(65);
    ASSERT_EQ("00:01", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmm(3600);
    ASSERT_EQ("01:00", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmm(5400);
    ASSERT_EQ("01:30", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmm(5410);
    ASSERT_EQ("01:30", std::string(str));
    free(str);
}

TEST(TogglApiTest, toggl_parse_duration_string_into_seconds) {
    ASSERT_EQ(0, toggl_parse_duration_string_into_seconds(0));
    ASSERT_EQ(0, toggl_parse_duration_string_into_seconds(""));
    ASSERT_EQ(15, toggl_parse_duration_string_into_seconds("15 seconds"));
}

TEST(TogglApiTest, toggl_password_forgot) {
    testing::App app;
    toggl_password_forgot(app.ctx());
    ASSERT_EQ("https://www.toggl.com/forgot-password",
              testing::testresult::url);
}

TEST(TogglApiTest, toggl_set_environment) {
    testing::App app;

    toggl_set_environment(app.ctx(), "test");

    char *env = toggl_environment(app.ctx());
    std::string res(env);
    free(env);
    ASSERT_EQ("test", res);
}

TEST(TogglApiTest, testing_set_logged_in_user) {
    std::string json = loadTestData();
    testing::App app;
    _Bool res = testing_set_logged_in_user(app.ctx(), json.c_str());
    ASSERT_TRUE(res);
    ASSERT_EQ(uint64_t(10471231), testing::testresult::user_id);
}

TEST(TogglApiTest, toggl_disable_update_check) {
    testing::App app;
    toggl_disable_update_check(app.ctx());
}

TEST(TogglApiTest, toggl_logout) {
    std::string json = loadTestData();
    testing::App app;
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    ASSERT_EQ(uint64_t(10471231), testing::testresult::user_id);
    ASSERT_TRUE(toggl_logout(app.ctx()));
    ASSERT_EQ(uint64_t(0), testing::testresult::user_id);
}

TEST(TogglApiTest, toggl_clear_cache) {
    std::string json = loadTestData();
    testing::App app;
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    ASSERT_EQ(uint64_t(10471231), testing::testresult::user_id);
    ASSERT_TRUE(toggl_clear_cache(app.ctx()));
    ASSERT_EQ(uint64_t(0), testing::testresult::user_id);
}

TEST(TogglApiTest, toggl_debug) {
    toggl_debug("Test 123");
}

TEST(TogglApiTest, toggl_set_idle_seconds) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    toggl_set_idle_seconds(app.ctx(), 0);
    ASSERT_EQ("", testing::testresult::idle_since);
    ASSERT_EQ(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("", testing::testresult::idle_duration);
    ASSERT_EQ("", testing::testresult::idle_guid);

    toggl_set_idle_seconds(app.ctx(), (5*60)-1);
    ASSERT_EQ("", testing::testresult::idle_since);
    ASSERT_EQ(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("", testing::testresult::idle_duration);
    ASSERT_EQ("", testing::testresult::idle_guid);

    toggl_set_idle_seconds(app.ctx(), (5*60));
    ASSERT_EQ("", testing::testresult::idle_since);
    ASSERT_EQ(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("", testing::testresult::idle_duration);
    ASSERT_EQ("", testing::testresult::idle_guid);

    toggl_set_idle_seconds(app.ctx(), 0);
    ASSERT_EQ("", testing::testresult::idle_since);
    ASSERT_EQ(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("", testing::testresult::idle_duration);
    ASSERT_EQ("", testing::testresult::idle_guid);

    ASSERT_TRUE(toggl_start(app.ctx(), "test", "", 0, 0));

    toggl_set_idle_seconds(app.ctx(), 5*60);
    ASSERT_EQ("", testing::testresult::idle_since);
    ASSERT_EQ(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("", testing::testresult::idle_duration);
    ASSERT_EQ("", testing::testresult::idle_guid);

    toggl_set_idle_seconds(app.ctx(), 0);
    ASSERT_EQ("", testing::testresult::idle_since);

    ASSERT_TRUE(toggl_set_time_entry_duration(app.ctx(),
                testing::testresult::timer_state.GUID().c_str(),
                "301 seconds"));

    toggl_set_idle_seconds(app.ctx(), 5*60);
    toggl_set_idle_seconds(app.ctx(), 0);
    ASSERT_NE("", testing::testresult::idle_since);

    ASSERT_NE(std::string::npos,
              testing::testresult::idle_since.find("You have been idle since"));
    ASSERT_NE(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("(5 minutes)", testing::testresult::idle_duration);
    ASSERT_NE("", testing::testresult::idle_guid);
}

TEST(TogglApiTest, toggl_open_in_browser) {
    testing::App app;
    toggl_open_in_browser(app.ctx());
}

TEST(TogglApiTest, toggl_get_support) {
    testing::App app;
    toggl_get_support(app.ctx());
    ASSERT_EQ("http://support.toggl.com/toggl-on-my-desktop/",
              testing::testresult::url);
}

TEST(TogglApiTest, toggl_set_api_url) {
    testing::App app;
    toggl_set_api_url(app.ctx(), "https://localhost:1234/api/v8");
}

TEST(TogglApiTest, toggl_set_websocket_url) {
    testing::App app;
    toggl_set_websocket_url(app.ctx(), "https://localhost:1234/ws");
}

TEST(TogglApiTest, toggl_login) {
    testing::App app;
    toggl_login(app.ctx(), "username", "password");
}

TEST(TogglApiTest, toggl_google_login) {
    testing::App app;
    toggl_google_login(app.ctx(), "token");
}

TEST(TogglApiTest, toggl_sync) {
    testing::App app;
    toggl_sync(app.ctx());
}

TEST(TogglApiTest, toggl_add_project) {
    testing::App app;

    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid = "07fba193-91c4-0ec8-2894-820df0548a8f";
    uint64_t wid = 0;
    uint64_t cid = 0;
    std::string project_name("");
    _Bool is_private = false;

    testing::testresult::error = "";
    _Bool res = toggl_add_project(app.ctx(),
                                  guid.c_str(),
                                  wid,
                                  cid,
                                  project_name.c_str(),
                                  is_private);
    ASSERT_EQ("Please select a workspace",
              testing::testresult::error);
    ASSERT_FALSE(res);

    wid = 123456789;
    res = toggl_add_project(app.ctx(),
                            guid.c_str(),
                            wid,
                            cid,
                            project_name.c_str(),
                            is_private);
    ASSERT_EQ("Project name must not be empty",
              testing::testresult::error);
    ASSERT_FALSE(res);

    project_name = "A new project";
    testing::testresult::error = "";
    res = toggl_add_project(app.ctx(),
                            guid.c_str(),
                            wid,
                            cid,
                            project_name.c_str(),
                            is_private);
    ASSERT_EQ("", testing::testresult::error);
    ASSERT_TRUE(res);

    bool found(false);
    for (std::size_t i = 0; i < testing::testresult::projects.size(); i++) {
        if (project_name == testing::testresult::projects[i]) {
            found = true;
            break;
        }
    }
    ASSERT_TRUE(found);
}

TEST(TogglApiTest, toggl_continue) {
    testing::App app;

    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid("6c97dc31-582e-7662-1d6f-5e9d623b1685");

    testing::testresult::error = "";
    ASSERT_TRUE(toggl_continue(app.ctx(), guid.c_str()));
    ASSERT_NE(guid, testing::testresult::timer_state.GUID());
    ASSERT_EQ("More work", testing::testresult::timer_state.Description());
}

TEST(TogglApiTest, toggl_check_view_struct_size) {
    toggl_check_view_struct_size(
        sizeof(TogglTimeEntryView),
        sizeof(TogglAutocompleteView),
        sizeof(TogglGenericView),
        sizeof(TogglSettingsView));
}

TEST(TogglApiTest, toggl_view_time_entry_list) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    toggl_view_time_entry_list(app.ctx());
    ASSERT_EQ(std::size_t(5), testing::testresult::time_entries.size());
}

TEST(TogglApiTest, toggl_edit) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    std::string guid("6a958efd-0e9a-d777-7e19-001b2d7ced92");
    _Bool edit_running_time_entry = false;
    std::string focused_field("description");
    toggl_edit(app.ctx(), guid.c_str(), edit_running_time_entry,
               focused_field.c_str());
    ASSERT_EQ(guid, testing::testresult::editor_state.GUID());
}

TEST(TogglApiTest, toggl_set_online) {
    // App can trigger online state before lib is initialized
    toggl_set_online(0);

    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    toggl_set_online(app.ctx());
}

TEST(TogglApiTest, toggl_set_sleep) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    toggl_set_sleep(app.ctx());
}

TEST(TogglApiTest, toggl_set_wake) {
    // App can trigger awake state from computer before lib is initialized
    toggl_set_wake(0);

    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    toggl_set_wake(app.ctx());
}

TEST(TogglApiTest, toggl_timeline_toggle_recording) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    ASSERT_TRUE(toggl_timeline_is_recording_enabled(app.ctx()));

    ASSERT_TRUE(toggl_timeline_toggle_recording(app.ctx(), true));
    ASSERT_TRUE(toggl_timeline_is_recording_enabled(app.ctx()));

    ASSERT_TRUE(toggl_timeline_toggle_recording(app.ctx(), false));
    ASSERT_FALSE(toggl_timeline_is_recording_enabled(app.ctx()));
}

TEST(TogglApiTest, toggl_edit_preferences) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    testing::testresult::open_settings = false;
    toggl_edit_preferences(app.ctx());
    ASSERT_TRUE(testing::testresult::open_settings);
}

TEST(TogglApiTest, toggl_continue_latest) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    ASSERT_TRUE(toggl_continue_latest(app.ctx()));
    ASSERT_EQ("guidless entry", testing::testresult::timer_state.Description());
}

TEST(TogglApiTest, toggl_delete_time_entry) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    toggl_view_time_entry_list(app.ctx());
    ASSERT_EQ(std::size_t(5), testing::testresult::time_entries.size());

    std::string guid("6a958efd-0e9a-d777-7e19-001b2d7ced92");
    ASSERT_TRUE(toggl_delete_time_entry(app.ctx(), guid.c_str()));

    toggl_view_time_entry_list(app.ctx());
    ASSERT_EQ(std::size_t(4), testing::testresult::time_entries.size());
}

TEST(TogglApiTest, toggl_set_time_entry_duration) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid = "07fba193-91c4-0ec8-2894-820df0548a8f";

    ASSERT_TRUE(toggl_set_time_entry_duration(app.ctx(),
                guid.c_str(), "2 hours"));

    toggl_view_time_entry_list(app.ctx());
    TimeEntry te;
    for (std::size_t i = 0; i < testing::testresult::time_entries.size();
            i++) {
        if (testing::testresult::time_entries[i].GUID() == guid) {
            te = testing::testresult::time_entries[i];
            break;
        }
    }
    ASSERT_EQ(guid, te.GUID());
    ASSERT_EQ(7200, te.DurationInSeconds());
}

TEST(TogglApiTest, toggl_set_time_entry_description) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid = "07fba193-91c4-0ec8-2894-820df0548a8f";

    ASSERT_TRUE(toggl_set_time_entry_description(app.ctx(),
                guid.c_str(), "this is a nuclear test"));

    toggl_view_time_entry_list(app.ctx());
    TimeEntry te;
    for (std::size_t i = 0; i < testing::testresult::time_entries.size();
            i++) {
        if (testing::testresult::time_entries[i].GUID() == guid) {
            te = testing::testresult::time_entries[i];
            break;
        }
    }
    ASSERT_EQ(guid, te.GUID());
    ASSERT_EQ("this is a nuclear test", te.Description());
}

TEST(TogglApiTest, toggl_stop) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    testing::testresult::timer_state = TimeEntry();

    ASSERT_TRUE(toggl_start(app.ctx(), "test", "", 0, 0));
    ASSERT_FALSE(testing::testresult::timer_state.GUID().empty());

    ASSERT_TRUE(toggl_stop(app.ctx()));
    ASSERT_TRUE(testing::testresult::timer_state.GUID().empty());
}

TEST(TogglApiTest, toggl_set_time_entry_billable) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    testing::testresult::timer_state = TimeEntry();

    ASSERT_TRUE(toggl_start(app.ctx(), "test", "", 0, 0));

    std::string guid = testing::testresult::timer_state.GUID();
    ASSERT_FALSE(guid.empty());

    ASSERT_TRUE(toggl_set_time_entry_billable(app.ctx(), guid.c_str(), true));
    ASSERT_TRUE(testing::testresult::timer_state.Billable());

    ASSERT_TRUE(toggl_set_time_entry_billable(app.ctx(), guid.c_str(), false));
    ASSERT_FALSE(testing::testresult::timer_state.Billable());
}

TEST(TogglApiTest, toggl_set_time_entry_tags) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    testing::testresult::timer_state = TimeEntry();

    ASSERT_TRUE(toggl_start(app.ctx(), "test", "", 0, 0));

    std::string guid = testing::testresult::timer_state.GUID();
    ASSERT_FALSE(guid.empty());

    ASSERT_TRUE(toggl_set_time_entry_tags(app.ctx(), guid.c_str(), "a|b|c"));
    ASSERT_EQ("a|b|c", testing::testresult::timer_state.Tags());

    ASSERT_TRUE(toggl_set_time_entry_tags(app.ctx(), guid.c_str(), "a"));
    ASSERT_EQ("a", testing::testresult::timer_state.Tags());

    ASSERT_TRUE(toggl_set_time_entry_tags(app.ctx(), guid.c_str(), ""));
    ASSERT_EQ("", testing::testresult::timer_state.Tags());
}

TEST(TogglApiTest, toggl_discard_time_at) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    testing::testresult::timer_state = TimeEntry();

    // Start a time entry

    ASSERT_TRUE(toggl_start(app.ctx(), "test", "", 0, 0));

    Poco::UInt64 started = time(0);
    std::string guid = testing::testresult::timer_state.GUID();
    ASSERT_FALSE(guid.empty());

    // Discard the time entry at some point

    Poco::UInt64 stopped = time(0);
    ASSERT_TRUE(toggl_discard_time_at(app.ctx(), guid.c_str(), stopped, false));
    ASSERT_NE(guid, testing::testresult::timer_state.GUID());
    ASSERT_TRUE(testing::testresult::timer_state.GUID().empty());

    TimeEntry te;
    for (std::size_t i = 0; i < testing::testresult::time_entries.size();
            i++) {
        if (testing::testresult::time_entries[i].GUID() == guid) {
            te = testing::testresult::time_entries[i];
            break;
        }
    }
    ASSERT_EQ(guid, te.GUID());
    ASSERT_EQ(started, te.Start());
    ASSERT_EQ(stopped, te.Stop());

    // Start another time entry

    ASSERT_TRUE(toggl_start(app.ctx(), "test 2", "", 0, 0));

    started = time(0);
    guid = testing::testresult::timer_state.GUID();
    ASSERT_FALSE(guid.empty());

    // Discard the time entry, by creating a new one

    stopped = time(0);
    ASSERT_TRUE(toggl_discard_time_at(app.ctx(), guid.c_str(), stopped, true));
    ASSERT_NE(guid, testing::testresult::timer_state.GUID());

    te = TimeEntry();
    for (std::size_t i = 0; i < testing::testresult::time_entries.size();
            i++) {
        if (testing::testresult::time_entries[i].GUID() == guid) {
            te = testing::testresult::time_entries[i];
            break;
        }
    }
    ASSERT_EQ(guid, te.GUID());
    ASSERT_EQ(started, te.Start());
    ASSERT_EQ(stopped, te.Stop());

    // Check that a new time entry was created

    ASSERT_TRUE(!testing::testresult::timer_state.GUID().empty());
    ASSERT_EQ(stopped, testing::testresult::timer_state.Start());
    ASSERT_TRUE(testing::testresult::timer_state.IsTracking());
    ASSERT_EQ(te.Description(),
              testing::testresult::timer_state.Description());
}

TEST(TogglApiTest, toggl_feedback_send) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    ASSERT_TRUE(toggl_feedback_send(app.ctx(),
                                    "Help", "I need help", ""));
}

TEST(TogglApiTest, toggl_set_time_entry_date) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid("07fba193-91c4-0ec8-2894-820df0548a8f");

    toggl::TimeEntry te = testing::testresult::time_entry_by_guid(guid);
    Poco::DateTime datetime(Poco::Timestamp::fromEpochTime(te.Start()));
    ASSERT_EQ(2013, datetime.year());
    ASSERT_EQ(9, datetime.month());
    ASSERT_EQ(5, datetime.day());
    ASSERT_EQ(6, datetime.hour());
    ASSERT_EQ(33, datetime.minute());
    ASSERT_EQ(50, datetime.second());

    // 10/27/2014 @ 12:51pm in UTC.
    int unix_timestamp(1414414311);
    ASSERT_TRUE(toggl_set_time_entry_date(app.ctx(),
                                          guid.c_str(),
                                          unix_timestamp));

    te = testing::testresult::time_entry_by_guid(guid);
    datetime = Poco::DateTime(Poco::Timestamp::fromEpochTime(te.Start()));
    ASSERT_EQ(2014, datetime.year());
    ASSERT_EQ(10, datetime.month());
    ASSERT_EQ(27, datetime.day());
    ASSERT_EQ(7, datetime.hour());
    ASSERT_EQ(33, datetime.minute());
    ASSERT_EQ(50, datetime.second());
}

TEST(TogglApiTest, toggl_set_time_entry_start) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid("07fba193-91c4-0ec8-2894-820df0548a8f");

    toggl::TimeEntry te = testing::testresult::time_entry_by_guid(guid);
    Poco::DateTime datetime(Poco::Timestamp::fromEpochTime(te.Start()));
    ASSERT_EQ(2013, datetime.year());
    ASSERT_EQ(9, datetime.month());
    ASSERT_EQ(5, datetime.day());
    ASSERT_EQ(6, datetime.hour());
    ASSERT_EQ(33, datetime.minute());
    ASSERT_EQ(50, datetime.second());

    ASSERT_TRUE(toggl_set_time_entry_start(app.ctx(), guid.c_str(), "12:34"));

    te = testing::testresult::time_entry_by_guid(guid);
    Poco::LocalDateTime local =
        Poco::DateTime(Poco::Timestamp::fromEpochTime(te.Start()));
    ASSERT_EQ(2013, local.year());
    ASSERT_EQ(9, local.month());
    ASSERT_EQ(5, local.day());
    ASSERT_EQ(12, local.hour());
    ASSERT_EQ(34, local.minute());<commit_msg>Cannot test hour when setting date, its tz dependant (lib)<commit_after>// Copyright 2014 Toggl Desktop developers.

#include <vector>

#include "gtest/gtest.h"

#include "./../lib/include/toggl_api.h"
#include "./../toggl_api_private.h"
#include "./test_data.h"
#include "./../settings.h"
#include "./../proxy.h"

#include "Poco/FileStream.h"
#include "Poco/File.h"
#include "Poco/Path.h"
#include "Poco/DateTime.h"
#include "Poco/LocalDateTime.h"

namespace toggl {

namespace testing {

namespace testresult {

// on_url
std::string url("");

// on_reminder
std::string reminder_title("");
std::string reminder_informative_text("");
std::string error("");

// on_online_state
bool online_state(false);

// on_login
uint64_t user_id(0);

// on_display_settings
_Bool open_settings(false);
Settings settings;
bool use_proxy(false);
Proxy proxy;

// on_update
std::string update_channel("");

// on_display_idle_notification
std::string idle_guid("");
std::string idle_since("");
std::string idle_duration("");
uint64_t idle_started(0);

// on_display_timer_state
TimeEntry timer_state;

// on_project_autocomplete
std::vector<std::string> projects;

// on_time_entry_list
std::vector<TimeEntry> time_entries;

TimeEntry time_entry_by_guid(const std::string guid) {
    TimeEntry te;
    for (std::size_t i = 0; i < testing::testresult::time_entries.size();
            i++) {
        if (testing::testresult::time_entries[i].GUID() == guid) {
            te = testing::testresult::time_entries[i];
            break;
        }
    }
    return te;
}

// on_time_entry_editor
TimeEntry editor_state;
_Bool editor_open(false);
std::string editor_focused_field_name("");

}  // namespace testresult

void on_app(const _Bool open) {
}

void on_error(
    const char *errmsg,
    const _Bool user_error) {
    if (errmsg) {
        testresult::error = std::string(errmsg);
        return;
    }
    testresult::error = std::string("");
}

void on_update(
    const _Bool open,
    TogglUpdateView *view) {
    testresult::update_channel = std::string(view->UpdateChannel);
}

void on_online_state(const _Bool is_online, const char *reason) {
    testresult::online_state = is_online;
}

void on_url(const char *url) {
    testresult::url = std::string(url);
}

void on_login(const _Bool open, const uint64_t user_id) {
    testresult::user_id = user_id;
}

void on_reminder(const char *title, const char *informative_text) {
    testresult::reminder_title = std::string(title);
    testresult::reminder_informative_text = std::string(informative_text);
}

void on_time_entry_list(
    const _Bool open,
    TogglTimeEntryView *first) {
    testing::testresult::time_entries.clear();
    TogglTimeEntryView *it = first;
    while (it) {
        TimeEntry te;
        te.SetGUID(it->GUID);
        te.SetDurationInSeconds(it->DurationInSeconds);
        te.SetDescription(it->Description);
        te.SetStart(it->Started);
        te.SetStop(it->Ended);
        testing::testresult::time_entries.push_back(te);
        it = reinterpret_cast<TogglTimeEntryView *>(it->Next);
    }
}

void on_time_entry_autocomplete(TogglAutocompleteView *first) {
}

void on_project_autocomplete(TogglAutocompleteView *first) {
    testing::testresult::projects.clear();
    TogglAutocompleteView *it = first;
    while (it) {
        testing::testresult::projects.push_back(
            std::string(it->ProjectLabel));
        it = reinterpret_cast<TogglAutocompleteView *>(it->Next);
    }
}

void on_client_select(TogglGenericView *first) {
}

void on_workspace_select(TogglGenericView *first) {
}

void on_tags(TogglGenericView *first) {
}

void on_time_entry_editor(
    const _Bool open,
    TogglTimeEntryView *te,
    const char *focused_field_name) {
    testing::testresult::editor_state = TimeEntry();
    testing::testresult::editor_state.SetGUID(te->GUID);
    testing::testresult::editor_open = open;
    testing::testresult::editor_focused_field_name =
        std::string(focused_field_name);
}

void on_display_settings(
    const _Bool open,
    TogglSettingsView *settings) {

    testing::testresult::open_settings = open;

    testing::testresult::settings.use_idle_detection =
        settings->UseIdleDetection;
    testing::testresult::settings.menubar_timer = settings->MenubarTimer;
    testing::testresult::settings.reminder = settings->Reminder;
    testing::testresult::settings.dock_icon = settings->DockIcon;
    testing::testresult::settings.on_top = settings->OnTop;

    testing::testresult::use_proxy = settings->UseProxy;

    testing::testresult::proxy.host = std::string(settings->ProxyHost);
    testing::testresult::proxy.port = settings->ProxyPort;
    testing::testresult::proxy.username = std::string(settings->ProxyUsername);
    testing::testresult::proxy.password = std::string(settings->ProxyPassword);
}

void on_display_timer_state(TogglTimeEntryView *te) {
    testing::testresult::timer_state = TimeEntry();
    if (te) {
        testing::testresult::timer_state.SetStart(te->Started);
        testing::testresult::timer_state.SetGUID(te->GUID);
        testing::testresult::timer_state.SetDurationInSeconds(
            te->DurationInSeconds);
        testing::testresult::timer_state.SetDescription(te->Description);
        if (te->Tags) {
            testing::testresult::timer_state.SetTags(te->Tags);
        }
        testing::testresult::timer_state.SetBillable(te->Billable);
    }
}

void on_display_idle_notification(
    const char *guid,
    const char *since,
    const char *duration,
    const uint64_t started) {
    testing::testresult::idle_since = std::string(since);
    testing::testresult::idle_started = started;
    testing::testresult::idle_duration = std::string(duration);
    testing::testresult::idle_guid = std::string(guid);
}

void on_apply_settings(
    TogglSettingsView *settings) {
    testing::testresult::use_proxy = settings->UseProxy;
    testing::testresult::settings.use_idle_detection =
        settings->UseIdleDetection;
    testing::testresult::settings.menubar_timer =
        settings->MenubarTimer;
    testing::testresult::settings.dock_icon = settings->DockIcon;
    testing::testresult::settings.on_top = settings->OnTop;
}

class App {
 public:
    App() {
        Poco::File f(TESTDB);
        if (f.exists()) {
            f.remove(false);
        }

        toggl_set_log_path("test.log");

        ctx_ = toggl_context_init("tests", "0.1");

        poco_assert(toggl_set_db_path(ctx_, TESTDB));

        Poco::Path path("src/ssl/cacert.pem");
        toggl_set_cacert_path(ctx_, path.toString().c_str());

        toggl_on_show_app(ctx_, on_app);
        toggl_on_error(ctx_, on_error);
        toggl_on_update(ctx_, on_update);
        toggl_on_online_state(ctx_, on_online_state);
        toggl_on_login(ctx_, on_login);
        toggl_on_url(ctx_, on_url);
        toggl_on_reminder(ctx_, on_reminder);
        toggl_on_time_entry_list(ctx_, on_time_entry_list);
        toggl_on_time_entry_autocomplete(ctx_, on_time_entry_autocomplete);
        toggl_on_project_autocomplete(ctx_, on_project_autocomplete);
        toggl_on_workspace_select(ctx_, on_workspace_select);
        toggl_on_client_select(ctx_, on_client_select);
        toggl_on_tags(ctx_, on_tags);
        toggl_on_time_entry_editor(ctx_, on_time_entry_editor);
        toggl_on_settings(ctx_, on_display_settings);
        toggl_on_timer_state(ctx_, on_display_timer_state);
        toggl_on_idle_notification(ctx_, on_display_idle_notification);

        poco_assert(toggl_ui_start(ctx_));
    }
    ~App() {
        toggl_context_clear(ctx_);
        ctx_ = 0;
    }

    void *ctx() {
        return ctx_;
    }

 private:
    void *ctx_;
};

}  // namespace testing

TEST(TogglApiTest, toggl_context_init) {
    testing::App app;
}

TEST(TogglApiTest, toggl_set_settings) {
    testing::App app;

    ASSERT_TRUE(toggl_set_settings(app.ctx(),
                                   false, false, false, false, false, 5));

    ASSERT_FALSE(testing::testresult::settings.use_idle_detection);
    ASSERT_FALSE(testing::testresult::settings.menubar_timer);
    ASSERT_FALSE(testing::testresult::settings.dock_icon);
    ASSERT_FALSE(testing::testresult::settings.on_top);
    ASSERT_FALSE(testing::testresult::settings.reminder);

    ASSERT_TRUE(toggl_set_settings(app.ctx(),
                                   true, true, true, true, true, 5));

    ASSERT_TRUE(testing::testresult::settings.use_idle_detection);
    ASSERT_TRUE(testing::testresult::settings.menubar_timer);
    ASSERT_TRUE(testing::testresult::settings.dock_icon);
    ASSERT_TRUE(testing::testresult::settings.on_top);
    ASSERT_TRUE(testing::testresult::settings.reminder);
}

TEST(TogglApiTest, toggl_set_proxy_settings) {
    testing::App app;

    ASSERT_TRUE(toggl_set_proxy_settings(
        app.ctx(), 1, "localhost", 8000, "johnsmith", "secret"));

    ASSERT_TRUE(testing::testresult::use_proxy);
    ASSERT_EQ(std::string("localhost"),
              std::string(testing::testresult::proxy.host));
    ASSERT_EQ(8000,
              static_cast<int>(testing::testresult::proxy.port));
    ASSERT_EQ(std::string("johnsmith"),
              std::string(testing::testresult::proxy.username));
    ASSERT_EQ(std::string("secret"),
              std::string(testing::testresult::proxy.password));
}

TEST(TogglApiTest, toggl_set_update_channel) {
    testing::App app;

    std::string default_channel("stable");

    toggl_about(app.ctx());

    ASSERT_EQ(default_channel, testing::testresult::update_channel);

    // Also check that the API itself thinks the default channel is
    char *str = toggl_get_update_channel(app.ctx());
    ASSERT_EQ(default_channel, std::string(str));
    free(str);

    ASSERT_FALSE(toggl_set_update_channel(app.ctx(), "invalid"));

    // The channel should be the same in the API
    str = toggl_get_update_channel(app.ctx());
    ASSERT_EQ(default_channel, std::string(str));
    free(str);

    ASSERT_TRUE(toggl_set_update_channel(app.ctx(), "beta"));

    ASSERT_EQ(std::string("beta"), testing::testresult::update_channel);

    // The channel should have been changed in the API
    str = toggl_get_update_channel(app.ctx());
    ASSERT_EQ(std::string("beta"), std::string(str));
    free(str);

    ASSERT_TRUE(toggl_set_update_channel(app.ctx(), "dev"));

    ASSERT_EQ(std::string("dev"), testing::testresult::update_channel);

    // The channel should have been changed in the API
    str = toggl_get_update_channel(app.ctx());
    ASSERT_EQ(std::string("dev"), std::string(str));
    free(str);

    ASSERT_TRUE(toggl_set_update_channel(app.ctx(), "stable"));

    ASSERT_EQ(std::string("stable"), testing::testresult::update_channel);

    // The channel should have been changed in the API
    str = toggl_get_update_channel(app.ctx());
    ASSERT_EQ(std::string("stable"), std::string(str));
    free(str);
}

TEST(TogglApiTest, toggl_set_log_level) {
    toggl_set_log_level("trace");
}

TEST(TogglApiTest, toggl_format_duration_in_seconds_hhmmss) {
    char *str = toggl_format_duration_in_seconds_hhmmss(10);
    ASSERT_EQ("00:00:10", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmmss(60);
    ASSERT_EQ("00:01:00", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmmss(65);
    ASSERT_EQ("00:01:05", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmmss(3600);
    ASSERT_EQ("01:00:00", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmmss(5400);
    ASSERT_EQ("01:30:00", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmmss(5410);
    ASSERT_EQ("01:30:10", std::string(str));
    free(str);
}

TEST(TogglApiTest, toggl_format_duration_in_seconds_hhmm) {
    char *str  = toggl_format_duration_in_seconds_hhmm(10);
    ASSERT_EQ("00:00", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmm(60);
    ASSERT_EQ("00:01", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmm(65);
    ASSERT_EQ("00:01", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmm(3600);
    ASSERT_EQ("01:00", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmm(5400);
    ASSERT_EQ("01:30", std::string(str));
    free(str);

    str = toggl_format_duration_in_seconds_hhmm(5410);
    ASSERT_EQ("01:30", std::string(str));
    free(str);
}

TEST(TogglApiTest, toggl_parse_duration_string_into_seconds) {
    ASSERT_EQ(0, toggl_parse_duration_string_into_seconds(0));
    ASSERT_EQ(0, toggl_parse_duration_string_into_seconds(""));
    ASSERT_EQ(15, toggl_parse_duration_string_into_seconds("15 seconds"));
}

TEST(TogglApiTest, toggl_password_forgot) {
    testing::App app;
    toggl_password_forgot(app.ctx());
    ASSERT_EQ("https://www.toggl.com/forgot-password",
              testing::testresult::url);
}

TEST(TogglApiTest, toggl_set_environment) {
    testing::App app;

    toggl_set_environment(app.ctx(), "test");

    char *env = toggl_environment(app.ctx());
    std::string res(env);
    free(env);
    ASSERT_EQ("test", res);
}

TEST(TogglApiTest, testing_set_logged_in_user) {
    std::string json = loadTestData();
    testing::App app;
    _Bool res = testing_set_logged_in_user(app.ctx(), json.c_str());
    ASSERT_TRUE(res);
    ASSERT_EQ(uint64_t(10471231), testing::testresult::user_id);
}

TEST(TogglApiTest, toggl_disable_update_check) {
    testing::App app;
    toggl_disable_update_check(app.ctx());
}

TEST(TogglApiTest, toggl_logout) {
    std::string json = loadTestData();
    testing::App app;
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    ASSERT_EQ(uint64_t(10471231), testing::testresult::user_id);
    ASSERT_TRUE(toggl_logout(app.ctx()));
    ASSERT_EQ(uint64_t(0), testing::testresult::user_id);
}

TEST(TogglApiTest, toggl_clear_cache) {
    std::string json = loadTestData();
    testing::App app;
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    ASSERT_EQ(uint64_t(10471231), testing::testresult::user_id);
    ASSERT_TRUE(toggl_clear_cache(app.ctx()));
    ASSERT_EQ(uint64_t(0), testing::testresult::user_id);
}

TEST(TogglApiTest, toggl_debug) {
    toggl_debug("Test 123");
}

TEST(TogglApiTest, toggl_set_idle_seconds) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    toggl_set_idle_seconds(app.ctx(), 0);
    ASSERT_EQ("", testing::testresult::idle_since);
    ASSERT_EQ(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("", testing::testresult::idle_duration);
    ASSERT_EQ("", testing::testresult::idle_guid);

    toggl_set_idle_seconds(app.ctx(), (5*60)-1);
    ASSERT_EQ("", testing::testresult::idle_since);
    ASSERT_EQ(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("", testing::testresult::idle_duration);
    ASSERT_EQ("", testing::testresult::idle_guid);

    toggl_set_idle_seconds(app.ctx(), (5*60));
    ASSERT_EQ("", testing::testresult::idle_since);
    ASSERT_EQ(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("", testing::testresult::idle_duration);
    ASSERT_EQ("", testing::testresult::idle_guid);

    toggl_set_idle_seconds(app.ctx(), 0);
    ASSERT_EQ("", testing::testresult::idle_since);
    ASSERT_EQ(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("", testing::testresult::idle_duration);
    ASSERT_EQ("", testing::testresult::idle_guid);

    ASSERT_TRUE(toggl_start(app.ctx(), "test", "", 0, 0));

    toggl_set_idle_seconds(app.ctx(), 5*60);
    ASSERT_EQ("", testing::testresult::idle_since);
    ASSERT_EQ(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("", testing::testresult::idle_duration);
    ASSERT_EQ("", testing::testresult::idle_guid);

    toggl_set_idle_seconds(app.ctx(), 0);
    ASSERT_EQ("", testing::testresult::idle_since);

    ASSERT_TRUE(toggl_set_time_entry_duration(app.ctx(),
                testing::testresult::timer_state.GUID().c_str(),
                "301 seconds"));

    toggl_set_idle_seconds(app.ctx(), 5*60);
    toggl_set_idle_seconds(app.ctx(), 0);
    ASSERT_NE("", testing::testresult::idle_since);

    ASSERT_NE(std::string::npos,
              testing::testresult::idle_since.find("You have been idle since"));
    ASSERT_NE(uint64_t(0), testing::testresult::idle_started);
    ASSERT_EQ("(5 minutes)", testing::testresult::idle_duration);
    ASSERT_NE("", testing::testresult::idle_guid);
}

TEST(TogglApiTest, toggl_open_in_browser) {
    testing::App app;
    toggl_open_in_browser(app.ctx());
}

TEST(TogglApiTest, toggl_get_support) {
    testing::App app;
    toggl_get_support(app.ctx());
    ASSERT_EQ("http://support.toggl.com/toggl-on-my-desktop/",
              testing::testresult::url);
}

TEST(TogglApiTest, toggl_set_api_url) {
    testing::App app;
    toggl_set_api_url(app.ctx(), "https://localhost:1234/api/v8");
}

TEST(TogglApiTest, toggl_set_websocket_url) {
    testing::App app;
    toggl_set_websocket_url(app.ctx(), "https://localhost:1234/ws");
}

TEST(TogglApiTest, toggl_login) {
    testing::App app;
    toggl_login(app.ctx(), "username", "password");
}

TEST(TogglApiTest, toggl_google_login) {
    testing::App app;
    toggl_google_login(app.ctx(), "token");
}

TEST(TogglApiTest, toggl_sync) {
    testing::App app;
    toggl_sync(app.ctx());
}

TEST(TogglApiTest, toggl_add_project) {
    testing::App app;

    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid = "07fba193-91c4-0ec8-2894-820df0548a8f";
    uint64_t wid = 0;
    uint64_t cid = 0;
    std::string project_name("");
    _Bool is_private = false;

    testing::testresult::error = "";
    _Bool res = toggl_add_project(app.ctx(),
                                  guid.c_str(),
                                  wid,
                                  cid,
                                  project_name.c_str(),
                                  is_private);
    ASSERT_EQ("Please select a workspace",
              testing::testresult::error);
    ASSERT_FALSE(res);

    wid = 123456789;
    res = toggl_add_project(app.ctx(),
                            guid.c_str(),
                            wid,
                            cid,
                            project_name.c_str(),
                            is_private);
    ASSERT_EQ("Project name must not be empty",
              testing::testresult::error);
    ASSERT_FALSE(res);

    project_name = "A new project";
    testing::testresult::error = "";
    res = toggl_add_project(app.ctx(),
                            guid.c_str(),
                            wid,
                            cid,
                            project_name.c_str(),
                            is_private);
    ASSERT_EQ("", testing::testresult::error);
    ASSERT_TRUE(res);

    bool found(false);
    for (std::size_t i = 0; i < testing::testresult::projects.size(); i++) {
        if (project_name == testing::testresult::projects[i]) {
            found = true;
            break;
        }
    }
    ASSERT_TRUE(found);
}

TEST(TogglApiTest, toggl_continue) {
    testing::App app;

    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid("6c97dc31-582e-7662-1d6f-5e9d623b1685");

    testing::testresult::error = "";
    ASSERT_TRUE(toggl_continue(app.ctx(), guid.c_str()));
    ASSERT_NE(guid, testing::testresult::timer_state.GUID());
    ASSERT_EQ("More work", testing::testresult::timer_state.Description());
}

TEST(TogglApiTest, toggl_check_view_struct_size) {
    toggl_check_view_struct_size(
        sizeof(TogglTimeEntryView),
        sizeof(TogglAutocompleteView),
        sizeof(TogglGenericView),
        sizeof(TogglSettingsView));
}

TEST(TogglApiTest, toggl_view_time_entry_list) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    toggl_view_time_entry_list(app.ctx());
    ASSERT_EQ(std::size_t(5), testing::testresult::time_entries.size());
}

TEST(TogglApiTest, toggl_edit) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    std::string guid("6a958efd-0e9a-d777-7e19-001b2d7ced92");
    _Bool edit_running_time_entry = false;
    std::string focused_field("description");
    toggl_edit(app.ctx(), guid.c_str(), edit_running_time_entry,
               focused_field.c_str());
    ASSERT_EQ(guid, testing::testresult::editor_state.GUID());
}

TEST(TogglApiTest, toggl_set_online) {
    // App can trigger online state before lib is initialized
    toggl_set_online(0);

    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    toggl_set_online(app.ctx());
}

TEST(TogglApiTest, toggl_set_sleep) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    toggl_set_sleep(app.ctx());
}

TEST(TogglApiTest, toggl_set_wake) {
    // App can trigger awake state from computer before lib is initialized
    toggl_set_wake(0);

    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    toggl_set_wake(app.ctx());
}

TEST(TogglApiTest, toggl_timeline_toggle_recording) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));
    ASSERT_TRUE(toggl_timeline_is_recording_enabled(app.ctx()));

    ASSERT_TRUE(toggl_timeline_toggle_recording(app.ctx(), true));
    ASSERT_TRUE(toggl_timeline_is_recording_enabled(app.ctx()));

    ASSERT_TRUE(toggl_timeline_toggle_recording(app.ctx(), false));
    ASSERT_FALSE(toggl_timeline_is_recording_enabled(app.ctx()));
}

TEST(TogglApiTest, toggl_edit_preferences) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    testing::testresult::open_settings = false;
    toggl_edit_preferences(app.ctx());
    ASSERT_TRUE(testing::testresult::open_settings);
}

TEST(TogglApiTest, toggl_continue_latest) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    ASSERT_TRUE(toggl_continue_latest(app.ctx()));
    ASSERT_EQ("guidless entry", testing::testresult::timer_state.Description());
}

TEST(TogglApiTest, toggl_delete_time_entry) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    toggl_view_time_entry_list(app.ctx());
    ASSERT_EQ(std::size_t(5), testing::testresult::time_entries.size());

    std::string guid("6a958efd-0e9a-d777-7e19-001b2d7ced92");
    ASSERT_TRUE(toggl_delete_time_entry(app.ctx(), guid.c_str()));

    toggl_view_time_entry_list(app.ctx());
    ASSERT_EQ(std::size_t(4), testing::testresult::time_entries.size());
}

TEST(TogglApiTest, toggl_set_time_entry_duration) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid = "07fba193-91c4-0ec8-2894-820df0548a8f";

    ASSERT_TRUE(toggl_set_time_entry_duration(app.ctx(),
                guid.c_str(), "2 hours"));

    toggl_view_time_entry_list(app.ctx());
    TimeEntry te;
    for (std::size_t i = 0; i < testing::testresult::time_entries.size();
            i++) {
        if (testing::testresult::time_entries[i].GUID() == guid) {
            te = testing::testresult::time_entries[i];
            break;
        }
    }
    ASSERT_EQ(guid, te.GUID());
    ASSERT_EQ(7200, te.DurationInSeconds());
}

TEST(TogglApiTest, toggl_set_time_entry_description) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid = "07fba193-91c4-0ec8-2894-820df0548a8f";

    ASSERT_TRUE(toggl_set_time_entry_description(app.ctx(),
                guid.c_str(), "this is a nuclear test"));

    toggl_view_time_entry_list(app.ctx());
    TimeEntry te;
    for (std::size_t i = 0; i < testing::testresult::time_entries.size();
            i++) {
        if (testing::testresult::time_entries[i].GUID() == guid) {
            te = testing::testresult::time_entries[i];
            break;
        }
    }
    ASSERT_EQ(guid, te.GUID());
    ASSERT_EQ("this is a nuclear test", te.Description());
}

TEST(TogglApiTest, toggl_stop) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    testing::testresult::timer_state = TimeEntry();

    ASSERT_TRUE(toggl_start(app.ctx(), "test", "", 0, 0));
    ASSERT_FALSE(testing::testresult::timer_state.GUID().empty());

    ASSERT_TRUE(toggl_stop(app.ctx()));
    ASSERT_TRUE(testing::testresult::timer_state.GUID().empty());
}

TEST(TogglApiTest, toggl_set_time_entry_billable) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    testing::testresult::timer_state = TimeEntry();

    ASSERT_TRUE(toggl_start(app.ctx(), "test", "", 0, 0));

    std::string guid = testing::testresult::timer_state.GUID();
    ASSERT_FALSE(guid.empty());

    ASSERT_TRUE(toggl_set_time_entry_billable(app.ctx(), guid.c_str(), true));
    ASSERT_TRUE(testing::testresult::timer_state.Billable());

    ASSERT_TRUE(toggl_set_time_entry_billable(app.ctx(), guid.c_str(), false));
    ASSERT_FALSE(testing::testresult::timer_state.Billable());
}

TEST(TogglApiTest, toggl_set_time_entry_tags) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    testing::testresult::timer_state = TimeEntry();

    ASSERT_TRUE(toggl_start(app.ctx(), "test", "", 0, 0));

    std::string guid = testing::testresult::timer_state.GUID();
    ASSERT_FALSE(guid.empty());

    ASSERT_TRUE(toggl_set_time_entry_tags(app.ctx(), guid.c_str(), "a|b|c"));
    ASSERT_EQ("a|b|c", testing::testresult::timer_state.Tags());

    ASSERT_TRUE(toggl_set_time_entry_tags(app.ctx(), guid.c_str(), "a"));
    ASSERT_EQ("a", testing::testresult::timer_state.Tags());

    ASSERT_TRUE(toggl_set_time_entry_tags(app.ctx(), guid.c_str(), ""));
    ASSERT_EQ("", testing::testresult::timer_state.Tags());
}

TEST(TogglApiTest, toggl_discard_time_at) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    testing::testresult::timer_state = TimeEntry();

    // Start a time entry

    ASSERT_TRUE(toggl_start(app.ctx(), "test", "", 0, 0));

    Poco::UInt64 started = time(0);
    std::string guid = testing::testresult::timer_state.GUID();
    ASSERT_FALSE(guid.empty());

    // Discard the time entry at some point

    Poco::UInt64 stopped = time(0);
    ASSERT_TRUE(toggl_discard_time_at(app.ctx(), guid.c_str(), stopped, false));
    ASSERT_NE(guid, testing::testresult::timer_state.GUID());
    ASSERT_TRUE(testing::testresult::timer_state.GUID().empty());

    TimeEntry te;
    for (std::size_t i = 0; i < testing::testresult::time_entries.size();
            i++) {
        if (testing::testresult::time_entries[i].GUID() == guid) {
            te = testing::testresult::time_entries[i];
            break;
        }
    }
    ASSERT_EQ(guid, te.GUID());
    ASSERT_EQ(started, te.Start());
    ASSERT_EQ(stopped, te.Stop());

    // Start another time entry

    ASSERT_TRUE(toggl_start(app.ctx(), "test 2", "", 0, 0));

    started = time(0);
    guid = testing::testresult::timer_state.GUID();
    ASSERT_FALSE(guid.empty());

    // Discard the time entry, by creating a new one

    stopped = time(0);
    ASSERT_TRUE(toggl_discard_time_at(app.ctx(), guid.c_str(), stopped, true));
    ASSERT_NE(guid, testing::testresult::timer_state.GUID());

    te = TimeEntry();
    for (std::size_t i = 0; i < testing::testresult::time_entries.size();
            i++) {
        if (testing::testresult::time_entries[i].GUID() == guid) {
            te = testing::testresult::time_entries[i];
            break;
        }
    }
    ASSERT_EQ(guid, te.GUID());
    ASSERT_EQ(started, te.Start());
    ASSERT_EQ(stopped, te.Stop());

    // Check that a new time entry was created

    ASSERT_TRUE(!testing::testresult::timer_state.GUID().empty());
    ASSERT_EQ(stopped, testing::testresult::timer_state.Start());
    ASSERT_TRUE(testing::testresult::timer_state.IsTracking());
    ASSERT_EQ(te.Description(),
              testing::testresult::timer_state.Description());
}

TEST(TogglApiTest, toggl_feedback_send) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    ASSERT_TRUE(toggl_feedback_send(app.ctx(),
                                    "Help", "I need help", ""));
}

TEST(TogglApiTest, toggl_set_time_entry_date) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid("07fba193-91c4-0ec8-2894-820df0548a8f");

    toggl::TimeEntry te = testing::testresult::time_entry_by_guid(guid);
    Poco::DateTime datetime(Poco::Timestamp::fromEpochTime(te.Start()));
    ASSERT_EQ(2013, datetime.year());
    ASSERT_EQ(9, datetime.month());
    ASSERT_EQ(5, datetime.day());
    ASSERT_EQ(6, datetime.hour());
    ASSERT_EQ(33, datetime.minute());
    ASSERT_EQ(50, datetime.second());

    // 10/27/2014 @ 12:51pm in UTC.
    int unix_timestamp(1414414311);
    ASSERT_TRUE(toggl_set_time_entry_date(app.ctx(),
                                          guid.c_str(),
                                          unix_timestamp));

    te = testing::testresult::time_entry_by_guid(guid);
    datetime = Poco::DateTime(Poco::Timestamp::fromEpochTime(te.Start()));
    ASSERT_EQ(2014, datetime.year());
    ASSERT_EQ(10, datetime.month());
    ASSERT_EQ(27, datetime.day());
    ASSERT_EQ(33, datetime.minute());
    ASSERT_EQ(50, datetime.second());
}

TEST(TogglApiTest, toggl_set_time_entry_start) {
    testing::App app;
    std::string json = loadTestData();
    ASSERT_TRUE(testing_set_logged_in_user(app.ctx(), json.c_str()));

    std::string guid("07fba193-91c4-0ec8-2894-820df0548a8f");

    toggl::TimeEntry te = testing::testresult::time_entry_by_guid(guid);
    Poco::DateTime datetime(Poco::Timestamp::fromEpochTime(te.Start()));
    ASSERT_EQ(2013, datetime.year());
    ASSERT_EQ(9, datetime.month());
    ASSERT_EQ(5, datetime.day());
    ASSERT_EQ(6, datetime.hour());
    ASSERT_EQ(33, datetime.minute());
    ASSERT_EQ(50, datetime.second());

    ASSERT_TRUE(toggl_set_time_entry_start(app.ctx(), guid.c_str(), "12:34"));

    te = testing::testresult::time_entry_by_guid(guid);
    Poco::LocalDateTime local =
        Poco::DateTime(Poco::Timestamp::fromEpochTime(te.Start()));
    ASSERT_EQ(2013, local.year());
    ASSERT_EQ(9, local.month());
    ASSERT_EQ(5, local.day());
    ASSERT_EQ(12, local.hour());
    ASSERT_EQ(34, local.minute());
{'fraction_non_alphanumeric': 0.1481717416231367, 'fraction_numerical': 0.016291884316473437, 'mean_word_length': 4.157535934291581, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 79, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6784893', 'n_tokens_mistral': 22043, 'n_tokens_neox': 21616, 'n_words': 2964}