Skip to content

Upgrade nats.c to v3.12.0, add JetStream stream/consumer management#7

Draft
joshbradshaw11 wants to merge 6 commits intomainfrom
upgrade-nats-3.12-jetstream-mgmt
Draft

Upgrade nats.c to v3.12.0, add JetStream stream/consumer management#7
joshbradshaw11 wants to merge 6 commits intomainfrom
upgrade-nats-3.12-jetstream-mgmt

Conversation

@joshbradshaw11
Copy link
Copy Markdown
Collaborator

Summary

  • Upgrades nats.c from v3.10.1 to v3.12.0 (adds per-message TTL, pull consumer priority groups, ObjectStore support; requires OpenSSL 1.1.1+)
  • Adds JetStream stream management APIs: addStream, updateStream, deleteStream
  • Adds JetStream consumer management APIs: addConsumer, deleteConsumer
  • Adds per-message TTL support via JsPublishOptions::msgTTL (new in nats.c v3.11)
  • Rewrites integration tests to be fully self-contained — no nats CLI dependency, only nats-server required
  • Removes unused JSON config files (stream/consumer setup is now programmatic)

New API surface

// Stream management
JsStreamInfo addStream(const JsStreamConfig& config);
JsStreamInfo updateStream(const JsStreamConfig& config);
bool deleteStream(const QString& name);

// Consumer management
void addConsumer(const QString& stream, const JsConsumerConfig& config);
bool deleteConsumer(const QString& stream, const QString& consumer);

New types: JsStreamConfig, JsStreamState, JsStreamInfo, JsRetentionPolicy, JsStorageType, JsDiscardPolicy

Diffstat (M)

Category Files +Lines -Lines Size
Production 4 202 11 M
Tests 1 105 68 M
Test assets 3 0 31 S
CMake 1 5 2 XS

Testing

  • All JetStream integration tests pass against nats-server v2.12.3
  • Tests cover: stream CRUD, sync/async publish, pull subscribe with headers + ack, push subscribe
  • Core NATS tests have pre-existing failures (unrelated, depend on nats CLI)

🤖 Generated with Claude Code

…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>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.c FetchContent 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.

Comment thread test/test_jetstream.cpp Outdated
Comment on lines +68 to +72
js->addStream(JsStreamConfig{
.name = "TEST_STREAM",
.subjects = {"test.>"},
.storage = JsStorageType::Memory,
});
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);

Copilot uses AI. Check for mistakes.
Comment thread test/test_jetstream.cpp Outdated
Comment on lines +53 to +72
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,
});
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread test/test_jetstream.cpp Outdated
Comment on lines 76 to 83
if (js) {
js->deleteStream("TEST_STREAM");
}
js = nullptr;
client.reset();

natsServer.close();
natsServer.waitForFinished();
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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();
}
}

Copilot uses AI. Check for mistakes.
joshbradshaw11 and others added 5 commits March 23, 2026 15:25
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants