From 16596efe51e848abf259c679d0bb7ac00381423b Mon Sep 17 00:00:00 2001 From: Mother 6000 Date: Wed, 10 Jun 2026 12:19:50 +0000 Subject: [PATCH 1/7] Align lifecycle API with older_than and compress action --- CHANGELOG.md | 1 + src/reduct/client.h | 4 ++-- src/reduct/internal/serialisation.cc | 9 +++++++-- tests/reduct/lifecycle_api_test.cc | 6 +++--- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7245b..a503ee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add lifecycle `compress` action support and rename lifecycle age field from `max_age` to `older_than` to match the ReductStore API, [PR-129](https://github.com/reductstore/reduct-cpp/pull/129) - Add lifecycle policy API support, [PR-128](https://github.com/reductstore/reduct-cpp/pull/128) ## 1.19.1 - 2026-04-21 diff --git a/src/reduct/client.h b/src/reduct/client.h index 0b0b450..2e13971 100644 --- a/src/reduct/client.h +++ b/src/reduct/client.h @@ -288,7 +288,7 @@ class IClient { /** * Lifecycle information */ - enum class LifecycleType { kDelete }; + enum class LifecycleType { kDelete, kCompress }; enum class LifecycleMode { kEnabled, kDisabled, kDryRun }; @@ -308,7 +308,7 @@ class IClient { LifecycleType type = LifecycleType::kDelete; // Lifecycle action type std::string bucket; // Bucket to apply lifecycle policy std::vector entries; // Entries to process. If empty, all matching entries are used. - std::string max_age; // Maximum record age + std::string older_than; // Process records older than this duration std::optional interval; // Interval between lifecycle runs std::optional when; // Lifecycle condition LifecycleMode mode = LifecycleMode::kEnabled; // Lifecycle mode diff --git a/src/reduct/internal/serialisation.cc b/src/reduct/internal/serialisation.cc index 2c20b39..7bfcb23 100644 --- a/src/reduct/internal/serialisation.cc +++ b/src/reduct/internal/serialisation.cc @@ -61,6 +61,9 @@ IClient::LifecycleType ParseLifecycleType(const nlohmann::json& type_json) { if (type == "delete") { return IClient::LifecycleType::kDelete; } + if (type == "compress") { + return IClient::LifecycleType::kCompress; + } throw std::invalid_argument("Invalid lifecycle type: " + type); } @@ -97,6 +100,8 @@ std::string LifecycleTypeToString(IClient::LifecycleType type) { switch (type) { case IClient::LifecycleType::kDelete: return "delete"; + case IClient::LifecycleType::kCompress: + return "compress"; } throw std::invalid_argument("Invalid lifecycle type"); @@ -327,7 +332,7 @@ Result LifecycleSettingsToJsonString(IClient::LifecycleSettings json_data["type"] = LifecycleTypeToString(settings.type); json_data["bucket"] = settings.bucket; json_data["entries"] = settings.entries; - json_data["max_age"] = settings.max_age; + json_data["older_than"] = settings.older_than; if (settings.interval) { json_data["interval"] = *settings.interval; } @@ -363,7 +368,7 @@ Result ParseFullLifecycleInfo(const nlohmann::json& .type = settings.contains("type") ? ParseLifecycleType(settings.at("type")) : IClient::LifecycleType::kDelete, .bucket = settings.at("bucket"), .entries = settings.at("entries"), - .max_age = settings.at("max_age"), + .older_than = settings.at("older_than"), .mode = settings.contains("mode") ? ParseLifecycleMode(settings.at("mode")) : IClient::LifecycleMode::kEnabled, }; diff --git a/tests/reduct/lifecycle_api_test.cc b/tests/reduct/lifecycle_api_test.cc index abf5798..6ad3411 100644 --- a/tests/reduct/lifecycle_api_test.cc +++ b/tests/reduct/lifecycle_api_test.cc @@ -12,10 +12,10 @@ namespace { IClient::LifecycleSettings DefaultSettings() { return { - .type = IClient::LifecycleType::kDelete, + .type = IClient::LifecycleType::kCompress, .bucket = "test_bucket_1", .entries = {"entry-1"}, - .max_age = "1h", + .older_than = "1h", .interval = "10m", .mode = IClient::LifecycleMode::kEnabled, }; @@ -63,7 +63,7 @@ TEST_CASE("reduct::Client should update a lifecycle", "[lifecycle_api][1_20]") { REQUIRE(err == Error::kOk); settings.entries = {"entry-2"}; - settings.max_age = "2h"; + settings.older_than = "2h"; settings.interval = "20m"; err = ctx.client->UpdateLifecycle("test_lifecycle", settings); REQUIRE(err == Error::kOk); From e59c548da7142bbe30e15c1f9df7db3365153f54 Mon Sep 17 00:00:00 2001 From: Mother 6000 Date: Wed, 10 Jun 2026 12:20:04 +0000 Subject: [PATCH 2/7] Update changelog for PR #130 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a503ee0..40fc2aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add lifecycle `compress` action support and rename lifecycle age field from `max_age` to `older_than` to match the ReductStore API, [PR-129](https://github.com/reductstore/reduct-cpp/pull/129) +- Add lifecycle `compress` action support and rename lifecycle age field from `max_age` to `older_than` to match the ReductStore API, [PR-130](https://github.com/reductstore/reduct-cpp/pull/130) - Add lifecycle policy API support, [PR-128](https://github.com/reductstore/reduct-cpp/pull/128) ## 1.19.1 - 2026-04-21 From b2c3896c641711d7c9378435ca982cd0554bc4d2 Mon Sep 17 00:00:00 2001 From: Mother 6000 Date: Wed, 10 Jun 2026 12:26:58 +0000 Subject: [PATCH 3/7] Fix lifecycle tests for compress restrictions --- tests/reduct/lifecycle_api_test.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/reduct/lifecycle_api_test.cc b/tests/reduct/lifecycle_api_test.cc index 6ad3411..e2e7127 100644 --- a/tests/reduct/lifecycle_api_test.cc +++ b/tests/reduct/lifecycle_api_test.cc @@ -12,7 +12,7 @@ namespace { IClient::LifecycleSettings DefaultSettings() { return { - .type = IClient::LifecycleType::kCompress, + .type = IClient::LifecycleType::kDelete, .bucket = "test_bucket_1", .entries = {"entry-1"}, .older_than = "1h", @@ -35,6 +35,7 @@ TEST_CASE("reduct::Client should get list of lifecycles", "[lifecycle_api][1_20] TEST_CASE("reduct::Client should create a lifecycle", "[lifecycle_api][1_20]") { Fixture ctx; auto settings = DefaultSettings(); + settings.type = IClient::LifecycleType::kCompress; auto err = ctx.client->CreateLifecycle("test_lifecycle", settings); REQUIRE(err == Error::kOk); From b1be43c81a200efd836c7788b86ed178bec2137d Mon Sep 17 00:00:00 2001 From: Mother 6000 Date: Wed, 10 Jun 2026 12:35:18 +0000 Subject: [PATCH 4/7] Stabilize tests against system bucket changes --- tests/fixture.h | 31 +++++++++++++++++---------- tests/reduct/server_api_test.cc | 37 ++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/tests/fixture.h b/tests/fixture.h index e3b62f2..c462abc 100644 --- a/tests/fixture.h +++ b/tests/fixture.h @@ -24,19 +24,28 @@ struct Fixture { } client = IClient::Build(url, opts); - auto bucket_list = client->GetBucketList(); - if (bucket_list.error) { - throw std::runtime_error(fmt::format("Failed to get bucket list: {}", bucket_list.error.ToString())); - } - for (auto&& info : client->GetBucketList().result) { - if (!info.name.starts_with("test_bucket")) { - continue; + auto cleanup_test_buckets = [&]() { + auto bucket_list = client->GetBucketList(); + if (bucket_list.error) { + throw std::runtime_error(fmt::format("Failed to get bucket list: {}", bucket_list.error.ToString())); } - std::unique_ptr bucket = client->GetBucket(info.name); - [[maybe_unused]] auto ret = bucket->Remove(); - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); // wait for bucket removal + bool removed = false; + for (auto&& info : bucket_list.result) { + if (!info.name.starts_with("test_bucket")) { + continue; + } + std::unique_ptr bucket = client->GetBucket(info.name); + [[maybe_unused]] auto ret = bucket->Remove(); + removed = true; + } + + return removed; + }; + + while (cleanup_test_buckets()) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } for (auto&& t : client->GetTokenList().result) { if (!t.name.starts_with("test_token")) { diff --git a/tests/reduct/server_api_test.cc b/tests/reduct/server_api_test.cc index e42d470..13b4ef9 100644 --- a/tests/reduct/server_api_test.cc +++ b/tests/reduct/server_api_test.cc @@ -21,7 +21,7 @@ TEST_CASE("reduct::Client should get info", "[server_api]") { REQUIRE(err == Error::kOk); REQUIRE(info.version >= "1.10.0"); - REQUIRE(info.bucket_count == 2); + REQUIRE(info.bucket_count >= 2); REQUIRE(info.usage == 234); REQUIRE(info.uptime.count() >= 1); REQUIRE(info.oldest_record.time_since_epoch() == s(1)); @@ -38,19 +38,28 @@ TEST_CASE("reduct::Client should list buckets", "[server_api]") { auto [list, err] = ctx.client->GetBucketList(); REQUIRE_FALSE(list.empty()); - REQUIRE(list[0].name == "test_bucket_1"); - REQUIRE(list[0].size == 156); - REQUIRE(list[0].entry_count == 2); - REQUIRE(list[0].oldest_record.time_since_epoch() == s(1)); - REQUIRE(list[0].latest_record.time_since_epoch() == s(4)); - REQUIRE(list[0].status == IBucket::Status::kReady); - - REQUIRE(list[1].name == "test_bucket_2"); - REQUIRE(list[1].size == 78); - REQUIRE(list[1].entry_count == 1); - REQUIRE(list[1].oldest_record.time_since_epoch() == s(5)); - REQUIRE(list[1].latest_record.time_since_epoch() == s(6)); - REQUIRE(list[1].status == IBucket::Status::kReady); + + std::vector test_buckets; + for (const auto& bucket : list) { + if (bucket.name.starts_with("test_bucket_")) { + test_buckets.push_back(bucket); + } + } + + REQUIRE(test_buckets.size() == 2); + REQUIRE(test_buckets[0].name == "test_bucket_1"); + REQUIRE(test_buckets[0].size == 156); + REQUIRE(test_buckets[0].entry_count == 2); + REQUIRE(test_buckets[0].oldest_record.time_since_epoch() == s(1)); + REQUIRE(test_buckets[0].latest_record.time_since_epoch() == s(4)); + REQUIRE(test_buckets[0].status == IBucket::Status::kReady); + + REQUIRE(test_buckets[1].name == "test_bucket_2"); + REQUIRE(test_buckets[1].size == 78); + REQUIRE(test_buckets[1].entry_count == 1); + REQUIRE(test_buckets[1].oldest_record.time_since_epoch() == s(5)); + REQUIRE(test_buckets[1].latest_record.time_since_epoch() == s(6)); + REQUIRE(test_buckets[1].status == IBucket::Status::kReady); } TEST_CASE("reduct::Client should return error", "[server_api]") { From 0453d5aa94dd702f4fb6305eb9a96a58dd557f4d Mon Sep 17 00:00:00 2001 From: Mother 6000 Date: Thu, 11 Jun 2026 05:53:21 +0000 Subject: [PATCH 5/7] Fix bucket info type in server API test --- tests/reduct/server_api_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reduct/server_api_test.cc b/tests/reduct/server_api_test.cc index 13b4ef9..86f8449 100644 --- a/tests/reduct/server_api_test.cc +++ b/tests/reduct/server_api_test.cc @@ -39,7 +39,7 @@ TEST_CASE("reduct::Client should list buckets", "[server_api]") { REQUIRE_FALSE(list.empty()); - std::vector test_buckets; + std::vector test_buckets; for (const auto& bucket : list) { if (bucket.name.starts_with("test_bucket_")) { test_buckets.push_back(bucket); From 25243c6e60b7ba9570a340df3c3c297b332f1f50 Mon Sep 17 00:00:00 2001 From: Mother 6000 Date: Thu, 11 Jun 2026 08:11:30 +0000 Subject: [PATCH 6/7] Relax usage assertion for system events --- tests/reduct/server_api_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reduct/server_api_test.cc b/tests/reduct/server_api_test.cc index 86f8449..dfa064a 100644 --- a/tests/reduct/server_api_test.cc +++ b/tests/reduct/server_api_test.cc @@ -22,7 +22,7 @@ TEST_CASE("reduct::Client should get info", "[server_api]") { REQUIRE(info.version >= "1.10.0"); REQUIRE(info.bucket_count >= 2); - REQUIRE(info.usage == 234); + REQUIRE(info.usage >= 234); REQUIRE(info.uptime.count() >= 1); REQUIRE(info.oldest_record.time_since_epoch() == s(1)); REQUIRE(info.latest_record.time_since_epoch() == s(6)); From d10cb1b62708acca8e8de2a2cd917fbd18c9ba76 Mon Sep 17 00:00:00 2001 From: Mother 6000 Date: Thu, 11 Jun 2026 08:22:57 +0000 Subject: [PATCH 7/7] Disable system events in CI test harness --- .github/actions/run-tests/action.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/actions/run-tests/action.yml b/.github/actions/run-tests/action.yml index 685eb54..bdb33d7 100644 --- a/.github/actions/run-tests/action.yml +++ b/.github/actions/run-tests/action.yml @@ -17,6 +17,10 @@ inputs: description: "Enable audit in ReductStore during tests" required: false default: "true" + system-events-enabled: + description: "Enable $system event storage in ReductStore during tests" + required: false + default: "false" runs: using: "composite" steps: @@ -25,6 +29,7 @@ runs: run: docker run -p 8383:8383 -v ${PWD}:/workdir --env RS_API_TOKEN=${{inputs.api-token}} --env RS_AUDIT_ENABLED=${{inputs.audit-enabled}} + --env RS_SYSTEM_EVENTS_ENABLED=${{inputs.system-events-enabled}} --env RS_EXT_PATH=/tmp --name reduct-store -d reduct/store:${{inputs.reductstore-version}}