Upgrade nats.c to v3.12.0, add JetStream stream/consumer management#7
Upgrade nats.c to v3.12.0, add JetStream stream/consumer management#7joshbradshaw11 wants to merge 6 commits intomainfrom
Conversation
…t APIs - Bump nats.c from v3.10.1 to v3.12.0 (per-message TTL, pull consumer priority groups, ObjectStore support, OpenSSL 1.1.1+ required) - Add JetStream stream management: addStream, updateStream, deleteStream - Add JetStream consumer management: addConsumer, deleteConsumer - Add per-message TTL support via JsPublishOptions::msgTTL - New types: JsStreamConfig, JsStreamState, JsStreamInfo, and enums JsRetentionPolicy, JsStorageType, JsDiscardPolicy - Rewrite integration tests to be fully self-contained (no nats CLI dependency, only nats-server required) - Remove unused JSON config files (stream/consumer setup is now programmatic) - Fix CMake test infrastructure: add enable_testing() and find_package(Qt6 Test) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR upgrades the bundled nats.c dependency to v3.12.0 and expands the QtNats JetStream wrapper with stream/consumer management plus per-message TTL support, while updating JetStream integration tests to be self-contained (no nats CLI).
Changes:
- Upgrade
nats.cFetchContent version to v3.12.0. - Add JetStream stream/consumer management APIs and related public types (
JsStreamConfig,JsStreamInfo, enums). - Update JetStream integration tests to create streams/consumers programmatically and remove JSON test assets.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
CMakeLists.txt |
Bumps nats.c version and enables Qt Test for integration tests. |
src/qtnats/qtnats.h |
Introduces new public JetStream stream-management types/enums and API surface; adds msgTTL publish option. |
src/qtnats/qtnats_p.h |
Adds RAII deleters for new nats.c info structs and conversion helper for JsStreamConfig; wires msgTTL into jsPubOptions. |
src/qtnats/qtnats_p.cpp |
Adds conversion from jsStreamInfo to JsStreamInfo. |
src/qtnats/jetstream.cpp |
Implements addStream/updateStream/deleteStream and addConsumer/deleteConsumer. |
test/test_jetstream.cpp |
Rewrites JetStream integration tests to avoid nats CLI and manage streams/consumers via the new APIs. |
test/stream_config.json |
Removed (stream config now created programmatically). |
test/pull_consumer_config.json |
Removed (consumer config now created programmatically). |
test/push_consumer_config.json |
Removed (consumer config now created programmatically). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| js->addStream(JsStreamConfig{ | ||
| .name = "TEST_STREAM", | ||
| .subjects = {"test.>"}, | ||
| .storage = JsStorageType::Memory, | ||
| }); |
There was a problem hiding this comment.
This test uses C++20 designated initializers (e.g., JsStreamConfig{ .name = ... }), but the project is configured for C++17 (CMAKE_CXX_STANDARD 17). This will fail to compile on standards-conforming C++17 toolchains (notably MSVC). Please either switch these initializations to C++17-compatible aggregate/constructor initialization, or bump the project/test C++ standard to 20 if that’s acceptable.
| js->addStream(JsStreamConfig{ | |
| .name = "TEST_STREAM", | |
| .subjects = {"test.>"}, | |
| .storage = JsStorageType::Memory, | |
| }); | |
| JsStreamConfig config; | |
| config.name = "TEST_STREAM"; | |
| config.subjects = {"test.>"}; | |
| config.storage = JsStorageType::Memory; | |
| js->addStream(config); |
| void JetStreamTestCase::initTestCase() { | ||
| // It might be nice to use something like CMRC to statically link the test config files; that's another dependency | ||
| // though, so for now we'll just verify that the directory actually exists. | ||
| QVERIFY2( | ||
| QDir::setCurrent("../qtnats/test"), | ||
| "Could not find test directory — run the test binary from the build directory, or update the path to match " | ||
| "your build directory" | ||
| ); | ||
|
|
||
| connect(&natsServer, &QProcess::stateChanged, [](QProcess::ProcessState newState) { | ||
| cout << "nats-server: " << qPrintable(enumToString(newState)) << endl; | ||
| }); | ||
|
|
||
| natsServer.start("nats-server", QStringList() << "-js"); | ||
| natsServer.waitForStarted(); | ||
| QVERIFY(natsServer.waitForStarted()); | ||
| QTest::qWait(1000); | ||
|
|
||
| natsCli.start("nats", QStringList() << "stream" << "add" << "--config=stream_config.json"); | ||
| natsCli.waitForFinished(); | ||
| client = std::make_unique<Client>(); | ||
| client->connectToServer(QUrl("nats://localhost:4222")); | ||
|
|
||
| js = client->jetStream(); | ||
|
|
||
| // Create a shared stream for the publish/subscribe tests | ||
| js->addStream(JsStreamConfig{ | ||
| .name = "TEST_STREAM", | ||
| .subjects = {"test.>"}, | ||
| .storage = JsStorageType::Memory, | ||
| }); |
There was a problem hiding this comment.
initTestCase() now calls connectToServer(), jetStream(), and addStream(), all of which can throw QtNats::Exception/JetStreamException on failure. Since initTestCase() doesn’t catch exceptions, a startup failure (server not in PATH, port in use, connection failure, etc.) can terminate the test process instead of producing a clean test failure. Wrap the body of initTestCase() in a try/catch and use QFAIL() (or QVERIFY_EXCEPTION_THROWN-style handling) so the suite fails gracefully.
| if (js) { | ||
| js->deleteStream("TEST_STREAM"); | ||
| } | ||
| js = nullptr; | ||
| client.reset(); | ||
|
|
||
| natsServer.close(); | ||
| natsServer.waitForFinished(); |
There was a problem hiding this comment.
cleanupTestCase() calls js->deleteStream() which can throw on network/JetStream errors. Cleanup functions should be resilient: if an exception escapes here it can mask earlier failures and/or crash the test runner. Consider wrapping cleanup operations in try/catch (and best-effort cleanup), and ensure the nats-server process is always terminated even if stream deletion fails.
| if (js) { | |
| js->deleteStream("TEST_STREAM"); | |
| } | |
| js = nullptr; | |
| client.reset(); | |
| natsServer.close(); | |
| natsServer.waitForFinished(); | |
| // Best-effort cleanup: do not let exceptions escape from here. | |
| try { | |
| if (js) { | |
| js->deleteStream("TEST_STREAM"); | |
| } | |
| } catch (const QException& e) { | |
| cerr << "Error deleting TEST_STREAM in cleanupTestCase: " << e.what() << endl; | |
| } catch (const std::exception& e) { | |
| cerr << "Std exception in cleanupTestCase while deleting TEST_STREAM: " << e.what() << endl; | |
| } catch (...) { | |
| cerr << "Unknown exception in cleanupTestCase while deleting TEST_STREAM" << endl; | |
| } | |
| js = nullptr; | |
| client.reset(); | |
| // Ensure the nats-server process is always terminated. | |
| if (natsServer.state() != QProcess::NotRunning) { | |
| natsServer.close(); | |
| if (!natsServer.waitForFinished(3000)) { | |
| natsServer.kill(); | |
| natsServer.waitForFinished(); | |
| } | |
| } |
Replace nats CLI process invocations with a second QtNats Client instance for publish, request/reply, and async request/reply tests. All tests now only require nats-server, not the nats CLI tool. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bump CMAKE_CXX_STANDARD from 17 to 20 (designated initializers are already used throughout the codebase and require C++20 on MSVC) - Wrap initTestCase() in try/catch so connection/stream creation failures produce clean QFAIL instead of crashing the test runner - Make cleanupTestCase() resilient: catch exceptions from deleteStream, kill nats-server if it doesn't stop within 3 seconds Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Creates a stream with maxMsgs=5, publishes 10 messages, then verifies only the last 5 are retained. Exercises the discard-old retention policy end-to-end through stream creation, publishing, and pull subscribe. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both use maxMsgs=3 with 5 publishes. DiscardOld keeps messages 3-5 (oldest evicted). DiscardNew keeps messages 1-3 (new publishes rejected with JetStreamException once the stream is full). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
addStream,updateStream,deleteStreamaddConsumer,deleteConsumerJsPublishOptions::msgTTL(new in nats.c v3.11)natsCLI dependency, onlynats-serverrequiredNew API surface
New types:
JsStreamConfig,JsStreamState,JsStreamInfo,JsRetentionPolicy,JsStorageType,JsDiscardPolicyDiffstat (M)
Testing
natsCLI)🤖 Generated with Claude Code