diff --git a/.github/workflows/cpp-tui-ci.yml b/.github/workflows/cpp-tui-ci.yml new file mode 100644 index 000000000..5eb409a2d --- /dev/null +++ b/.github/workflows/cpp-tui-ci.yml @@ -0,0 +1,148 @@ +name: C++ TUI CI +on: + push: + branches: [main] + paths: + - 'cpp-tui/**' + pull_request: + branches: [main] + paths: + - 'cpp-tui/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y clang-format + + - name: Run clang-format check + working-directory: cpp-tui/taskscpp + run: | + make format + if ! git diff --exit-code; then + echo "❌ Code style issues found. Run 'make format' to fix." + exit 1 + fi + + build: + name: Build + runs-on: ubuntu-latest + needs: lint + timeout-minutes: 15 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake clang build-essential + + - name: Create .env file + run: | + echo "DITTO_APP_ID=${{ secrets.DITTO_APP_ID }}" > .env + echo "DITTO_PLAYGROUND_TOKEN=${{ secrets.DITTO_PLAYGROUND_TOKEN }}" >> .env + echo "DITTO_AUTH_URL=${{ secrets.DITTO_AUTH_URL }}" >> .env + echo "DITTO_WEBSOCKET_URL=${{ secrets.DITTO_WEBSOCKET_URL }}" >> .env + + - name: Download and setup Ditto C++ SDK + working-directory: cpp-tui/taskscpp + run: make download-sdk + + - name: Build application + working-directory: cpp-tui/taskscpp + run: | + awk -f scripts/generate_env.awk ../../.env > src/env.h + make build + + integration-test: + name: Integration Test + runs-on: ubuntu-latest + needs: [lint, build] + timeout-minutes: 10 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake clang build-essential + + - name: Create .env file + run: | + echo "DITTO_APP_ID=${{ secrets.DITTO_APP_ID }}" > .env + echo "DITTO_PLAYGROUND_TOKEN=${{ secrets.DITTO_PLAYGROUND_TOKEN }}" >> .env + echo "DITTO_AUTH_URL=${{ secrets.DITTO_AUTH_URL }}" >> .env + echo "DITTO_WEBSOCKET_URL=${{ secrets.DITTO_WEBSOCKET_URL }}" >> .env + + - name: Insert test document into Ditto Cloud + run: | + TIMESTAMP=$(date +%s) + INVERTED_TIMESTAMP=$((9999999999 - TIMESTAMP)) + DOC_ID="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" + DOC_TITLE="${INVERTED_TIMESTAMP}_ci_test_${GITHUB_RUN_ID}_${GITHUB_RUN_NUMBER}" + + RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ + -H 'Content-type: application/json' \ + -H "Authorization: Bearer ${{ secrets.DITTO_API_KEY }}" \ + -d "{ + \"statement\": \"INSERT INTO tasks DOCUMENTS (:newTask) ON ID CONFLICT DO UPDATE\", + \"args\": { + \"newTask\": { + \"_id\": \"${DOC_ID}\", + \"title\": \"${DOC_TITLE}\", + \"done\": false, + \"deleted\": false + } + } + }" \ + "https://${{ secrets.DITTO_API_URL }}/api/v4/store/execute") + + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + BODY=$(echo "$RESPONSE" | head -n-1) + + if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 201 ]; then + echo "GITHUB_TEST_DOC_TITLE=${DOC_TITLE}" >> $GITHUB_ENV + else + echo "❌ Failed to insert document. HTTP Status: $HTTP_CODE" + echo "Response: $BODY" + exit 1 + fi + + - name: Download and setup Ditto C++ SDK + working-directory: cpp-tui/taskscpp + run: make download-sdk + + - name: Build and run integration test + working-directory: cpp-tui/taskscpp + env: + GITHUB_TEST_DOC_TITLE: ${{ env.GITHUB_TEST_DOC_TITLE }} + GITHUB_RUN_ID: ${{ github.run_id }} + GITHUB_RUN_NUMBER: ${{ github.run_number }} + run: | + awk -f scripts/generate_env.awk ../../.env > src/env.h + mkdir -p build + g++ -std=c++17 -I./src -I./sdk -I./third_party/cxxopts/include \ + tests/integration_test.cpp \ + src/task.cpp src/tasks_peer.cpp src/tasks_log.cpp \ + -L./sdk -lditto -ldl -lrt -pthread \ + -o build/integration_test + ./build/integration_test \ No newline at end of file diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 61dd6b39d..eae68d627 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -102,66 +102,6 @@ jobs: working-directory: javascript-tui run: npm run test - cpp: - name: CPP Quickstart (ubuntu-latest) - strategy: - matrix: - os: ["ubuntu-latest"] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v4 - - - name: Install dependencies - run: | - if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then - sudo apt-get update - sudo apt-get install -y cmake build-essential - elif [ "${{ matrix.os }}" = "macos-latest" ]; then - brew install cmake - fi - - - name: Create .env file - run: | - echo "DITTO_APP_ID=test_app_id" > .env - echo "DITTO_PLAYGROUND_TOKEN=test_playground_token" >> .env - echo "DITTO_AUTH_URL=https://auth.example.com" >> .env - echo "DITTO_WEBSOCKET_URL=wss://websocket.example.com" >> .env - - - name: Download and setup Ditto SDK - working-directory: cpp-tui/taskscpp/sdk - run: | - # Determine architecture and download appropriate Ditto SDK - if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then - ARCH=$(uname -m) - if [ "$ARCH" = "x86_64" ]; then - curl -O https://software.ditto.live/cpp-linux-x86_64/Ditto/4.10.0/dist/Ditto.tar.gz - elif [ "$ARCH" = "aarch64" ]; then - curl -O https://software.ditto.live/cpp-linux-aarch64/Ditto/4.10.0/dist/Ditto.tar.gz - fi - fi - # Extract the SDK - tar xvfz Ditto.tar.gz - - - name: Configure CMake - working-directory: cpp-tui/taskscpp - run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release - - - name: Build - working-directory: cpp-tui/taskscpp - run: cmake --build build - - - name: Test app startup - working-directory: cpp-tui/taskscpp - run: | - # Test that the app can start and show help - ./build/taskscpp --help - echo "✅ Help command works" - - # Test that the app can show SDK version - ./build/taskscpp --ditto-sdk-version - echo "✅ SDK version command works" kotlin-multiplatform: name: Kotlin Multiplatform (ubuntu-24.04) diff --git a/cpp-tui/README.md b/cpp-tui/README.md index d3c69e6c1..91294e5ef 100644 --- a/cpp-tui/README.md +++ b/cpp-tui/README.md @@ -1,5 +1,14 @@ # Ditto C++ Console QuickStart Tasks App +## Platform Support + +**Linux Console Application** - Runs on modern Linux distributions with full Ditto sync capabilities: + +- ✅ **Linux (x64)**: Ubuntu 20.04 LTS and later +- ✅ **Linux (AArch64)**: Ubuntu 22.04 LTS and later + +For Android development, see the separate `android-cpp` project. + ## Prerequisites After you have completed the [common prerequisites] you will need the following: @@ -7,11 +16,9 @@ After you have completed the [common prerequisites] you will need the following: - A C++ compiler (clang or gcc) - CMake 3.10 or later - Make +- **Linux operating system** (Ubuntu 20.04+ recommended) -Download and unpack the C++ Ditto SDK for your platform by following the -instructions in the [Ditto C++ Install Guide](https://docs.ditto.live/install-guides/cpp). -Then, copy the `Ditto.h` and `libditto.a` files from the SDK into the `sdk/` -subdirectory of this project. +The build system will automatically download the Ditto C++ SDK when you run `make build` on Linux. No manual SDK installation is required! ## Documentation @@ -27,7 +34,9 @@ Assuming you have the prerequisites installed, you can build and run the app by 1. Create an application at . Make note of the app ID and online playground token. 2. Copy the `.env.sample` file at the top level of the `quickstart` repo to `.env` and add your app ID and online playground token. -3. In a shell, navigate to the `quickstart/cpp-tui/taskscpp` directory and run the command `make build` to build the C++ application. +3. In a shell, navigate to the `quickstart/cpp-tui/taskscpp` directory and run the command `make build` to automatically download the Ditto SDK and build the C++ application. + +The build system will detect your Linux architecture (x86_64/arm64) and download the appropriate Ditto C++ SDK version automatically. ## Running the Application diff --git a/cpp-tui/taskscpp/CMakeLists.txt b/cpp-tui/taskscpp/CMakeLists.txt index ff7845ebd..ef745ff15 100644 --- a/cpp-tui/taskscpp/CMakeLists.txt +++ b/cpp-tui/taskscpp/CMakeLists.txt @@ -77,3 +77,29 @@ endif() # Add dependency on cxxopts library target_include_directories(taskscpp PRIVATE third_party/cxxopts/include) + +# Enable testing +option(BUILD_TESTS "Build tests" ON) + +if(BUILD_TESTS) + enable_testing() + + # Integration tests + add_executable(integration_test + tests/integration_test.cpp + src/task.cpp + src/tasks_peer.cpp + src/tasks_log.cpp + ) + target_include_directories(integration_test PRIVATE src sdk third_party/cxxopts/include) + target_link_libraries(integration_test PRIVATE ${CMAKE_SOURCE_DIR}/sdk/libditto.a) + add_dependencies(integration_test env_h) + add_test(NAME IntegrationTests COMMAND integration_test) + + # Custom test target + add_custom_target(run_tests + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + DEPENDS integration_test + COMMENT "Running integration tests" + ) +endif() diff --git a/cpp-tui/taskscpp/Makefile b/cpp-tui/taskscpp/Makefile index 0f89ceecf..3cd41b2d1 100644 --- a/cpp-tui/taskscpp/Makefile +++ b/cpp-tui/taskscpp/Makefile @@ -1,6 +1,26 @@ # CMake build type: Debug, Release, RelWithDebInfo BUILD_TYPE ?= Debug +# Ditto SDK version and platform detection +DITTO_SDK_VERSION ?= 4.12.0 +PLATFORM := $(shell uname -s | tr '[:upper:]' '[:lower:]') +ARCH := $(shell uname -m) + +# Determine Ditto SDK platform string +ifeq ($(PLATFORM),linux) + DITTO_PLATFORM = cpp-linux-$(ARCH) +else ifeq ($(PLATFORM),darwin) + ifeq ($(ARCH),arm64) + DITTO_PLATFORM = cpp-macos-arm64 + else + DITTO_PLATFORM = cpp-macos-x86_64 + endif +else + $(error Unsupported platform: $(PLATFORM)) +endif + +DITTO_SDK_URL = https://software.ditto.live/$(DITTO_PLATFORM)/Ditto/$(DITTO_SDK_VERSION)/dist/Ditto.tar.gz + # Utility paths CMAKE ?= cmake CTEST ?= ctest @@ -26,10 +46,28 @@ help: ## (default) Displays this message @grep -E '^[A-Z0-9_-]* ?\?=.*?##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = " ?\\?=.*?##"}; {printf $(MAKEFILE_FMT), $$1, $$2}' .PHONY: build -build: ## Generates all targets +build: download-sdk ## Generates all targets (downloads SDK first) $(CMAKE) -B $(BUILD_DIR) . -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -Wno-dev $(CMAKE) --build $(BUILD_DIR) --parallel +.PHONY: download-sdk +download-sdk: ## Downloads and installs the Ditto C++ SDK + @echo "📥 Downloading Ditto C++ SDK v$(DITTO_SDK_VERSION) for $(DITTO_PLATFORM)..." + @if [ ! -f sdk/Ditto.h ] || [ ! -f sdk/libditto.a ]; then \ + mkdir -p sdk; \ + if curl -L -f $(DITTO_SDK_URL) | tar xz --strip-components=0 -C sdk/; then \ + echo "✅ Ditto C++ SDK v$(DITTO_SDK_VERSION) installed successfully"; \ + else \ + echo "❌ Failed to download SDK for $(DITTO_PLATFORM)"; \ + echo "💡 The Ditto C++ SDK only supports Linux x64/ARM64 platforms"; \ + echo "💡 macOS and Windows are not supported by Ditto C++ SDK"; \ + echo "💡 See: https://docs.ditto.live/reference/compatibility/cpp"; \ + exit 1; \ + fi; \ + else \ + echo "✅ Ditto C++ SDK already installed"; \ + fi + .PHONY: build-no-tui build-no-tui: ## Generates all targets, without TUI support $(CMAKE) -B $(BUILD_DIR) . -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -Wno-dev -DDITTO_QUICKSTART_TUI=OFF @@ -69,6 +107,22 @@ lint: build ## Runs clang-format -n and clang-tidy on all .cpp and .h files $(CLANG_FORMAT) --dry-run --Werror $(CPP_SRC_FILES) $(CLANG_TIDY) -p $(BUILD_DIR) $(CPP_SRC_FILES) +.PHONY: test +test: build ## Build and run all tests + cd $(BUILD_DIR) && $(CTEST) --output-on-failure + +.PHONY: test-unit +test-unit: build ## Build and run unit tests only + cd $(BUILD_DIR) && ./unit_test + +.PHONY: test-integration +test-integration: build ## Build and run integration tests only + cd $(BUILD_DIR) && ./integration_test + +.PHONY: test-script +test-script: build ## Run tests using the shell script + tests/run_tests.sh + .PHONY: clean clean: ## Removes all generated files and directories - rm -r $(BUILD_DIR) diff --git a/cpp-tui/taskscpp/sdk/.gitignore b/cpp-tui/taskscpp/sdk/.gitignore index 2a657dfd1..f1285eac1 100644 --- a/cpp-tui/taskscpp/sdk/.gitignore +++ b/cpp-tui/taskscpp/sdk/.gitignore @@ -1,2 +1 @@ -Ditto.h -libditto.a +# SDK files downloaded by CI diff --git a/cpp-tui/taskscpp/sdk/README.md b/cpp-tui/taskscpp/sdk/README.md deleted file mode 100644 index d8316c83d..000000000 --- a/cpp-tui/taskscpp/sdk/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# sdk Directory - -Copy the `Ditto.h` and `libditto.a` files from the Ditto C++ SDK to this -directory. - diff --git a/cpp-tui/taskscpp/src/main.cpp b/cpp-tui/taskscpp/src/main.cpp index 290f6f8ff..ac2dc4dea 100644 --- a/cpp-tui/taskscpp/src/main.cpp +++ b/cpp-tui/taskscpp/src/main.cpp @@ -191,14 +191,12 @@ int main(int argc, const char *argv[]) { opt_parse.count("online-playground-token") > 0 ? opt_parse["online-playground-token"].as() : DITTO_PLAYGROUND_TOKEN; - const auto websocket_url = - opt_parse.count("websocket-url") > 0 - ? opt_parse["websocket-url"].as() - : DITTO_WEBSOCKET_URL; - const auto auth_url = - opt_parse.count("auth-url") > 0 - ? opt_parse["auth-url"].as() - : DITTO_AUTH_URL; + const auto websocket_url = opt_parse.count("websocket-url") > 0 + ? opt_parse["websocket-url"].as() + : DITTO_WEBSOCKET_URL; + const auto auth_url = opt_parse.count("auth-url") > 0 + ? opt_parse["auth-url"].as() + : DITTO_AUTH_URL; const auto enable_cloud_sync = opt_parse.count("enable-cloud-sync") > 0; @@ -209,13 +207,8 @@ int main(int argc, const char *argv[]) { // The peer is destroyed at the end of this scope { - TasksPeer peer( - app_id, - online_playground_token, - websocket_url, - auth_url, - enable_cloud_sync, - persistence_dir); + TasksPeer peer(app_id, online_playground_token, websocket_url, auth_url, + enable_cloud_sync, persistence_dir); peer.insert_initial_tasks(); peer.start_sync(); diff --git a/cpp-tui/taskscpp/src/tasks_peer.cpp b/cpp-tui/taskscpp/src/tasks_peer.cpp index 955e3160c..c155c0591 100644 --- a/cpp-tui/taskscpp/src/tasks_peer.cpp +++ b/cpp-tui/taskscpp/src/tasks_peer.cpp @@ -35,44 +35,39 @@ static string to_json_string(const ditto::QueryResult &result) { result.items(), [](const ditto::QueryResultItem &item) { return item.json_string(); }); - const auto modified_document_ids = transform_container>( - result.mutated_document_ids(), - [](const ditto::DocumentId &id) { return id.to_string(); }); + const auto modified_document_ids = result.mutated_document_ids(); - nlohmann::json result_json = { - {"items", items}, {"modified_document_ids", modified_document_ids}}; + nlohmann::json result_json; + result_json["items"] = items; + result_json["modified_document_ids"] = modified_document_ids; return result_json.dump(); } /// Initialize a Ditto instance. -static shared_ptr init_ditto(string app_id, - string online_playground_token, - string websocket_url, - string auth_url, - bool enable_cloud_sync, - string persistence_dir) { +static shared_ptr +init_ditto(string app_id, string online_playground_token, string websocket_url, + string auth_url, bool enable_cloud_sync, string persistence_dir) { try { const auto identity = ditto::Identity::OnlinePlayground( - std::move(app_id), - std::move(online_playground_token), - enable_cloud_sync, - std::move(auth_url)); + std::move(app_id), std::move(online_playground_token), + enable_cloud_sync, std::move(auth_url)); auto ditto = std::make_shared(identity, std::move(persistence_dir)); - ditto->update_transport_config([websocket_url](ditto::TransportConfig &config) { - config.enable_all_peer_to_peer(); - config.connect.websocket_urls.insert(websocket_url); - }); + ditto->update_transport_config( + [websocket_url](ditto::TransportConfig &config) { + config.enable_all_peer_to_peer(); + config.connect.websocket_urls.insert(websocket_url); + }); // Required for compatibility with DQL. ditto->disable_sync_with_v3(); - // Disable DQL strict mode // https://docs.ditto.live/dql/strict-mode - const auto disableStrictModeCommand = "ALTER SYSTEM SET DQL_STRICT_MODE = false"; + const auto disableStrictModeCommand = + "ALTER SYSTEM SET DQL_STRICT_MODE = false"; const auto result = ditto->get_store().execute(disableStrictModeCommand); return ditto; @@ -90,29 +85,21 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) string select_tasks_query(bool include_deleted_tasks = false) { if (include_deleted_tasks) { - return "SELECT * FROM tasks ORDER BY _id"; + return "SELECT * FROM tasks ORDER BY title ASC"; } else { - return "SELECT * FROM tasks WHERE NOT deleted ORDER BY _id"; + return "SELECT * FROM tasks WHERE NOT deleted ORDER BY title ASC"; } } public: - Impl( - string app_id, - string online_playground_token, - string websocket_url, - string auth_url, - bool enable_cloud_sync, - string persistence_dir) + Impl(string app_id, string online_playground_token, string websocket_url, + string auth_url, bool enable_cloud_sync, string persistence_dir) : mtx(new mutex()), - ditto( - init_ditto( - std::move(app_id), - std::move(online_playground_token), - std::move(websocket_url), - std::move(auth_url), - enable_cloud_sync, // This is required to be set to false to use the correct URLs - std::move(persistence_dir))) {} + ditto(init_ditto(std::move(app_id), std::move(online_playground_token), + std::move(websocket_url), std::move(auth_url), + enable_cloud_sync, // This is required to be set to + // false to use the correct URLs + std::move(persistence_dir))) {} ~Impl() noexcept { try { @@ -131,7 +118,7 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) ditto->start_sync(); tasks_subscription = - ditto->sync().register_subscription("SELECT * FROM tasks"); + ditto->get_sync().register_subscription("SELECT * FROM tasks"); } void stop_sync() { @@ -153,9 +140,9 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) const auto command = "INSERT INTO tasks DOCUMENTS (:newTask)"; const auto result = ditto->get_store().execute(command, {{"newTask", task_args}}); - auto task_id = result.mutated_document_ids()[0].to_string(); - log_debug("Added task: " + task_id); - return task_id; + auto task_id = result.mutated_document_ids()[0]; + log_debug("Added task: " + task_id.to_string()); + return task_id.to_string(); } catch (const exception &err) { log_error("Failed to add task: " + string(err.what())); throw runtime_error("unable to add task: " + string(err.what())); @@ -400,20 +387,12 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) } }; // class TasksPeer::Impl -TasksPeer::TasksPeer( - string app_id, - string online_playground_token, - string websocket_url, - string auth_url, - bool enable_cloud_sync, - string persistence_dir) - : impl(new Impl( - std::move(app_id), - std::move(online_playground_token), - std::move(websocket_url), - std::move(auth_url), - enable_cloud_sync, - std::move(persistence_dir))) {} +TasksPeer::TasksPeer(string app_id, string online_playground_token, + string websocket_url, string auth_url, + bool enable_cloud_sync, string persistence_dir) + : impl(new Impl(std::move(app_id), std::move(online_playground_token), + std::move(websocket_url), std::move(auth_url), + enable_cloud_sync, std::move(persistence_dir))) {} TasksPeer::~TasksPeer() noexcept { try { diff --git a/cpp-tui/taskscpp/src/tasks_peer.h b/cpp-tui/taskscpp/src/tasks_peer.h index 8edbcf90e..3a76307cf 100644 --- a/cpp-tui/taskscpp/src/tasks_peer.h +++ b/cpp-tui/taskscpp/src/tasks_peer.h @@ -16,13 +16,9 @@ class TasksPeer { static std::string get_ditto_sdk_version(); /// Construct a new TasksPeer object. - TasksPeer( - std::string ditto_app_id, - std::string ditto_online_playground_token, - std::string ditto_websocket_url, - std::string ditto_auth_url, - bool enable_cloud_sync, - std::string ditto_persistence_dir); + TasksPeer(std::string ditto_app_id, std::string ditto_online_playground_token, + std::string ditto_websocket_url, std::string ditto_auth_url, + bool enable_cloud_sync, std::string ditto_persistence_dir); virtual ~TasksPeer() noexcept; diff --git a/cpp-tui/taskscpp/tests/integration_test.cpp b/cpp-tui/taskscpp/tests/integration_test.cpp new file mode 100644 index 000000000..a054fb788 --- /dev/null +++ b/cpp-tui/taskscpp/tests/integration_test.cpp @@ -0,0 +1,97 @@ +#include "../src/env.h" +#include "../src/task.h" +#include "../src/tasks_peer.h" +#include "../src/tasks_log.h" + +#include +#include +#include +#include +#include +#include + +using std::cout; +using std::endl; +using std::string; +using std::vector; +using std::unique_ptr; +using std::exception; +using std::chrono::seconds; +using std::chrono::high_resolution_clock; +using std::chrono::milliseconds; +using std::chrono::duration_cast; +using std::this_thread::sleep_for; + +/** + * Simple test that verifies GitHub-seeded document appears in synced task list + */ +int main() { + try { + cout << "C++ GitHub Seeded Document Test" << endl; + + // Get the exact document title that GitHub Actions seeded + const auto expected_title_env = getenv("GITHUB_TEST_DOC_TITLE"); + if (!expected_title_env || string(expected_title_env).empty()) { + cout << "FAIL: Missing GITHUB_TEST_DOC_TITLE environment variable" << endl; + std::exit(EXIT_FAILURE); + } + + string expected_title = string(expected_title_env); + cout << "Looking for seeded document: '" << expected_title << "'" << endl; + + // Initialize TasksPeer and start sync + cout << "Initializing Ditto sync..." << endl; + auto peer = unique_ptr(new TasksPeer( + DITTO_APP_ID, + DITTO_PLAYGROUND_TOKEN, + DITTO_WEBSOCKET_URL, + DITTO_AUTH_URL, + true, // enable_cloud_sync + "/tmp/cpp_integration_test" + )); + + peer->start_sync(); + cout << "Sync started, polling for document..." << endl; + + // Wait for sync and search for the exact document + const auto max_wait_seconds = 30; + const auto poll_interval_ms = 1000; + auto found = false; + + auto start_time = high_resolution_clock::now(); + + while (duration_cast(high_resolution_clock::now() - start_time).count() < max_wait_seconds && !found) { + auto elapsed = duration_cast(high_resolution_clock::now() - start_time).count(); + + vector tasks = peer->get_tasks(); + cout << "Checking " << tasks.size() << " synced tasks at " << elapsed << "s..." << endl; + + for (size_t i = 0; i < tasks.size(); i++) { + const auto& task = tasks[i]; + if (task.title == expected_title) { + cout << "SUCCESS: Found document '" << expected_title << "' at position " << i << endl; + found = true; + break; + } + } + + if (!found) { + sleep_for(milliseconds(poll_interval_ms)); + } + } + + auto final_elapsed = duration_cast(high_resolution_clock::now() - start_time).count(); + + if (found) { + cout << "PASS: GitHub Actions → Ditto Cloud → C++ SDK sync verified in " << final_elapsed << "s" << endl; + return 0; + } else { + cout << "FAIL: Document '" << expected_title << "' not found after " << final_elapsed << "s" << endl; + std::exit(EXIT_FAILURE); + } + + } catch (const exception& e) { + cout << "FAIL: Test exception: " << e.what() << endl; + std::exit(EXIT_FAILURE); + } +} \ No newline at end of file diff --git a/cpp-tui/taskscpp/tests/run_tests.sh b/cpp-tui/taskscpp/tests/run_tests.sh new file mode 100755 index 000000000..201251448 --- /dev/null +++ b/cpp-tui/taskscpp/tests/run_tests.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Test runner for C++ Ditto TUI application +# Compiles and runs both unit tests and integration tests + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +echo "🧪 C++ Ditto TUI Test Runner" +echo "============================" +echo "Project directory: $PROJECT_DIR" + +# Ensure we're in the right directory +cd "$PROJECT_DIR" + +# Check if env.h exists +if [ ! -f "src/env.h" ]; then + echo "❌ env.h not found. Run 'awk -f scripts/generate_env.awk ../../.env > src/env.h' first" + exit 1 +fi + +# Check if the main project is built +if [ ! -f "build/taskscpp" ]; then + echo "🔨 Main project not built, building first..." + make build +fi + +# Create test build directory +mkdir -p build/tests + +echo "" +echo "🏗️ Compiling Integration Tests..." +echo "=================================" +g++ -std=c++17 \ + -I./src -I./sdk -I./third_party/cxxopts/include \ + tests/integration_test.cpp \ + src/task.cpp src/tasks_peer.cpp src/tasks_log.cpp \ + -L./sdk -lditto \ + -o build/tests/integration_test \ + -pthread + +if [ $? -eq 0 ]; then + echo "✅ Integration tests compiled successfully" +else + echo "❌ Failed to compile integration tests" + echo "⚠️ Note: Integration tests require Ditto SDK (libditto.a) in sdk/ directory" + echo "📚 See README.md for SDK installation instructions" + exit 1 +fi + +echo "" +echo "🚀 Running Integration Tests..." +echo "===============================" +./build/tests/integration_test + +echo "" +echo "🎉 Integration Tests Completed Successfully!" +echo "==========================================" +echo "✅ Integration tests: PASSED" +echo "🎯 C++ Ditto TUI application validated!" \ No newline at end of file