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