Skip to content

2 MB message support#397

Closed
marcin-cebo wants to merge 19 commits into
masterfrom
2MB_message
Closed

2 MB message support#397
marcin-cebo wants to merge 19 commits into
masterfrom
2MB_message

Conversation

@marcin-cebo
Copy link
Copy Markdown
Contributor

@marcin-cebo marcin-cebo commented Feb 17, 2026

feat: Send messages up to 2 MB

feat: Reject oversized publishes client-side with a clear error

feat: New LogContentConfig with loggedMessageContentMaxBytes + loggedHttpResponseMaxBytes (0 disables, <0 unlimited)

feat: Fingerprint on publish, signal, subscribe and publishFileMessage debug logs

…ssages client gets socket close instead of 413 error.

I tried with “Expect: 100-continue” header that works for oversize messages but for regular messages e.g. 100KB causes server not sending 100 continue status code resulting in client waiting till timeout and not sending request body to server.
Fixed logging interceptor no longer consume the response body, so server error now properly reach CoreEndpoint
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds size-aware PublishEndpoint routing (choose V1 GET/POST or V2 POST by measured request size), a MESSAGE_TOO_LARGE error, non-consuming HTTP response logging, a new PublishService V2 POST, helper/test updates, and unit/integration tests validating routing, signature overhead, and client-side oversize rejection.

Changes

Large-message publish with dynamic V1/V2 routing

Layer / File(s) Summary
Error model and V2 service contract
pubnub-kotlin-core-api/src/commonMain/kotlin/com/pubnub/api/PubNubError.kt, pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/services/PublishService.kt
MESSAGE_TOO_LARGE error (code 184) and new Retrofit publishWithPostV2 endpoint for large messages.
Large-message routing in PublishEndpoint
pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/pubsub/PublishEndpoint.kt
Adds sizing constants, GET path signature overhead, helpers to compute UTF-8 POST body size and estimate GET path length, size-aware routing choosing V1 POST/GET or V2 POST, and v2 limit enforcement (throws MESSAGE_TOO_LARGE).
Non-consuming HTTP response logging
pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/logging/networkLogging/CustomPnHttpLoggingInterceptor.kt
Refactor to buffer/peek response bodies for logging without consuming the stream and return the original Response.
Test infrastructure updates
pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/test/Extensions.kt, pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishTest.kt
RemoteAction.await accepts optional seconds; PublishTest.FakeCall now builds realistic Retrofit request URLs including the encrypted message for size/path calculations.
Routing logic unit tests
pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishV2RoutingTest.kt
Unit tests covering POST/GET boundary conditions, secret-key signature overhead effects, large payloads, and client-side oversize rejection (>2MB) with mocked services and realistic fake Calls.
V2 POST WireMock test suite
pubnub-kotlin-impl/src/test/kotlin/com/pubnub/internal/suite/pubsub/PublishV2PostTestSuite.kt
WireMock-based suite validating that >32KB payloads are routed to the V2 publish endpoint and that request body JSON matches the serialized large message.
Kotlin integration tests
pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.kt, pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/PublishIntegrationTests.kt
Integration tests for large-message publish across V1/V2 routing, history/receive delivery, large JSON objects, progressive size cases (~100KB–2MB), crypto case, oversized rejection; one test switched to server.publish(...).
Java integration tests
pubnub-gson/pubnub-gson-impl/src/integrationTest/java/com/pubnub/api/integration/*
Integration tests (ignored) covering large-message publish limits, history/receive, JSON object round-trip, progressive sizes, crypto variant, and client-side oversize rejection; adds explicit // Intentionally left empty comments in several SubscribeCallback.status overrides.
PubNubUtil signature overhead test
pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/PubNubUtilTest.kt
New unit test asserting signature-related signed-query overhead does not exceed the constant budget used in GET-path estimations.
V2 POST Map-payload WireMock suite
pubnub-kotlin-impl/src/test/kotlin/com/pubnub/internal/suite/pubsub/PublishV2PostMapPayloadTestSuite.kt
WireMock-based suite validating V2 publish routing and JSON body matching when payload is a structured Map.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • kleewho
  • parfeon
  • jakub-grzesiowski
  • jguz-pubnub
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding support for 2 MB messages in PubNub, which is the primary objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2MB_message

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented May 14, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 130 complexity · 54 duplication

Metric Results
Complexity 130
Duplication 54

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/pubsub/PublishEndpoint.kt (1)

128-163: ⚖️ Poor tradeoff

Consider: V1 GET Call is built but potentially discarded.

The code builds v1GetCall (line 130) to calculate path length, but if routing to V2, that call is discarded. While this is acceptable for code clarity and the performance impact is likely negligible, consider whether lazy evaluation or path-length estimation without call construction would be cleaner. Not blocking—the current approach is straightforward and maintainable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/pubsub/PublishEndpoint.kt`
around lines 128 - 163, The code currently constructs v1GetCall via
retrofitManager.publishService.publish(...) just to measure path length and then
discards it when switching to V2; avoid this waste by either making v1GetCall
lazy or by changing the path-length calculation to operate on the message string
and query components instead of an HTTP call. Specifically, stop eagerly calling
publishService.publish(...) in PublishEndpoint.kt: use getParamMessage(message)
together with queryParams and configuration to estimate the GET path length (or
change calculateV1GetPathLengthBytes to accept the stringified message and
params), and only build/return v1GetCall when the path fits; keep the existing
V2 flow that uses getBodyMessage(message), calculateV1PostBodySizeBytes(payload)
and enforceV2PostBodySizeLimit before calling publishWithPostV2.
pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishV2RoutingTest.kt (1)

75-75: ⚡ Quick win

Replace empty function blocks to clear detekt warnings.

Use expression bodies (= Unit) for no-op overrides to resolve EmptyFunctionBlock warnings cleanly.

Suggested patch
-        override fun enqueue(callback: Callback<List<Any>>) {}
+        override fun enqueue(callback: Callback<List<Any>>) = Unit
@@
-        override fun cancel() {}
+        override fun cancel() = Unit
@@
-        override fun enqueue(callback: Callback<List<Any>>) {}
+        override fun enqueue(callback: Callback<List<Any>>) = Unit
@@
-        override fun cancel() {}
+        override fun cancel() = Unit

Also applies to: 79-79, 93-93, 97-97

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishV2RoutingTest.kt`
at line 75, The no-op override methods (e.g., override fun enqueue(callback:
Callback<List<Any>>) {}) cause Detekt EmptyFunctionBlock warnings; replace these
empty function blocks with expression bodies using = Unit for each no-op
override (including the occurrences at the other reported lines) so the
overrides become concise expression-bodied functions (refer to the
enqueue(callback: Callback<List<Any>>) override and other similar no-op
overrides in PublishV2RoutingTest).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@pubnub-kotlin/pubnub-kotlin-core-api/src/commonMain/kotlin/com/pubnub/api/PubNubError.kt`:
- Around line 253-256: Add a note to the release/changelog documenting that a
new public enum entry PubNubError.MESSAGE_TOO_LARGE (code 184) was introduced
and may break client code using exhaustive Kotlin when-expressions without an
else branch; explicitly instruct consumers to update their when blocks to handle
PubNubError.MESSAGE_TOO_LARGE or add an else/default branch, and reference the
PubNubError enum in the release notes so downstream users are aware of the
source-compatibility change.

In
`@pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/logging/networkLogging/CustomPnHttpLoggingInterceptor.kt`:
- Around line 94-113: The body-peeking logic in CustomPnHttpLoggingInterceptor
unconditionally buffers/clones the entire response and Base64-encodes non-text
bodies, causing heavy allocations on large responses; change the interceptor to
first check the configured logVerbosity/verbosity level (or a shouldLogBody
flag) before calling responseBody.source(), source.request(...), buffer.clone(),
buffer.readByteArray(), or Base64.getEncoder() so that buffering and encoding
only occur when body logging is enabled (keep the existing error handling and
UTF-8 fallback and preserve response readability when you do buffer by using the
same clone/read approach).

In
`@pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishV2RoutingTest.kt`:
- Around line 70-73: The test doubles FakeCallWithUrl and FakeCall violate
Retrofit's Call.clone() contract by returning the same instance; update their
clone() implementations to return a new independent Call instance (e.g., create
and return a new FakeCallWithUrl(url) for FakeCallWithUrl and a new
FakeCall(...) for FakeCall), preserving any constructor state (like url) so
retries/executions behave independently.

---

Nitpick comments:
In
`@pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/pubsub/PublishEndpoint.kt`:
- Around line 128-163: The code currently constructs v1GetCall via
retrofitManager.publishService.publish(...) just to measure path length and then
discards it when switching to V2; avoid this waste by either making v1GetCall
lazy or by changing the path-length calculation to operate on the message string
and query components instead of an HTTP call. Specifically, stop eagerly calling
publishService.publish(...) in PublishEndpoint.kt: use getParamMessage(message)
together with queryParams and configuration to estimate the GET path length (or
change calculateV1GetPathLengthBytes to accept the stringified message and
params), and only build/return v1GetCall when the path fits; keep the existing
V2 flow that uses getBodyMessage(message), calculateV1PostBodySizeBytes(payload)
and enforceV2PostBodySizeLimit before calling publishWithPostV2.

In
`@pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishV2RoutingTest.kt`:
- Line 75: The no-op override methods (e.g., override fun enqueue(callback:
Callback<List<Any>>) {}) cause Detekt EmptyFunctionBlock warnings; replace these
empty function blocks with expression bodies using = Unit for each no-op
override (including the occurrences at the other reported lines) so the
overrides become concise expression-bodied functions (refer to the
enqueue(callback: Callback<List<Any>>) override and other similar no-op
overrides in PublishV2RoutingTest).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: de259f5b-4800-484b-85d0-ab004b2bf4be

📥 Commits

Reviewing files that changed from the base of the PR and between 655047c and 7d52742.

📒 Files selected for processing (10)
  • pubnub-gson/pubnub-gson-impl/src/integrationTest/java/com/pubnub/api/integration/PublishIntegrationTests.java
  • pubnub-kotlin/pubnub-kotlin-core-api/src/commonMain/kotlin/com/pubnub/api/PubNubError.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/PublishIntegrationTests.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/test/Extensions.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/endpoints/pubsub/PublishEndpoint.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/logging/networkLogging/CustomPnHttpLoggingInterceptor.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/main/kotlin/com/pubnub/internal/services/PublishService.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishTest.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishV2RoutingTest.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/internal/suite/pubsub/PublishV2PostTestSuite.kt

Comment on lines +253 to 256
MESSAGE_TOO_LARGE(
code = 184,
message = "Message payload exceeds the maximum allowed size of 2 MiB.",
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Flag: New enum entry may break exhaustive when-expressions.

Adding MESSAGE_TOO_LARGE to the public PubNubError enum is a potential source-compatibility break for customer code with exhaustive when expressions (no else branch). Ensure release notes document this addition and advise customers to handle the new error or add an else branch.

As per coding guidelines, "Flag any addition, removal, rename, or reordering of entries in public enums (RetryableEndpointGroup, PNLogVerbosity, PNHeartbeatNotificationOptions) and sealed class hierarchies (RetryConfiguration variants) as a potential source-compatibility break for customers with exhaustive when-expressions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@pubnub-kotlin/pubnub-kotlin-core-api/src/commonMain/kotlin/com/pubnub/api/PubNubError.kt`
around lines 253 - 256, Add a note to the release/changelog documenting that a
new public enum entry PubNubError.MESSAGE_TOO_LARGE (code 184) was introduced
and may break client code using exhaustive Kotlin when-expressions without an
else branch; explicitly instruct consumers to update their when blocks to handle
PubNubError.MESSAGE_TOO_LARGE or add an else/default branch, and reference the
PubNubError enum in the release notes so downstream users are aware of the
source-compatibility change.

Comment on lines +70 to +73
class FakeCallWithUrl(private val url: String) : Call<List<Any>> {
override fun clone(): Call<List<Any>> = this

override fun execute(): Response<List<Any>> = Response.success(listOf("test1", "test2", "10"))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Retrofit 2, what is the expected behavior of retrofit2.Call.clone()for customCall implementations, and should it return a new call instance?

💡 Result:

In Retrofit 2, retrofit2.Call.clone is expected to create a new, identical Call instance that can be executed or enqueued even if the original Call has already been executed/enqueued [1][2]. In other words, clone should not return the same (already-used/busy) Call instance; it should return a fresh Call object that represents the same request parameters and can be used independently (e.g., for retries or polling) [1][2]. For custom Call implementations, implement clone to return a new Call that is “functionally the same” but independent in execution state (i.e., it can be enqueued/executed separately from the original). This typically means your clone should return a new wrapper Call instance and also clone any delegate Call it wraps, so that the delegate’s execution state is not shared [3]. So yes: clone should return a new call instance for custom Call implementations, not the same instance, per the contract “Create a new, identical call … which can be enqueued or executed even if this call has already been” [1][2].

Citations:


Return a new Call instance from clone() in test doubles.

Retrofit's Call.clone() contract requires returning a new, independent Call instance—not the same instance. Both FakeCallWithUrl and FakeCall currently return this, which violates the contract and can mask retry/re-execution behavior in tests.

Suggested patch
 class FakeCallWithUrl(private val url: String) : Call<List<Any>> {
-    override fun clone(): Call<List<Any>> = this
+    override fun clone(): Call<List<Any>> = FakeCallWithUrl(url)
@@
 class FakeCall : Call<List<Any>> {
-    override fun clone(): Call<List<Any>> = this
+    override fun clone(): Call<List<Any>> = FakeCall()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class FakeCallWithUrl(private val url: String) : Call<List<Any>> {
override fun clone(): Call<List<Any>> = this
override fun execute(): Response<List<Any>> = Response.success(listOf("test1", "test2", "10"))
class FakeCallWithUrl(private val url: String) : Call<List<Any>> {
override fun clone(): Call<List<Any>> = FakeCallWithUrl(url)
override fun execute(): Response<List<Any>> = Response.success(listOf("test1", "test2", "10"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/endpoints/pubsub/PublishV2RoutingTest.kt`
around lines 70 - 73, The test doubles FakeCallWithUrl and FakeCall violate
Retrofit's Call.clone() contract by returning the same instance; update their
clone() implementations to return a new independent Call instance (e.g., create
and return a new FakeCallWithUrl(url) for FakeCallWithUrl and a new
FakeCall(...) for FakeCall), preserving any constructor state (like url) so
retries/executions behave independently.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@pubnub-gson/pubnub-gson-impl/src/integrationTest/java/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.java`:
- Around line 113-136: The test adds an anonymous SubscribeCallback via
pubNub.addListener(...) but never removes it and also leaves an extra PubNub
client un-shutdown later; to fix, capture the SubscribeCallback instance in a
local variable (instead of anonymous), call pubNub.removeListener(theCallback)
after the Awaitility.await completes (or in a finally/teardown block), and
ensure any additional PubNub client created (the temporary client variable used
around lines ~233–245) is properly shutdown by calling its shutdown() (or
closing it in teardown) to avoid cross-test listener/resource leaks.
- Line 94: In LargeMessagePublishIntegrationTests replace the two fixed pauses
(calls to pause(3)) with condition-based waits: remove pause(3) and instead
poll/await until the expected post-publish condition is true (e.g., publish
completion/receipt, message present on subscriber, or a PubNub ack) with a
reasonable timeout and fail-on-timeout; locate the pause calls in the test
methods inside class LargeMessagePublishIntegrationTests and use the test
harness's existing wait/poll utilities (or implement a simple loop with sleep +
timeout) to check the specific condition that the test asserts before
proceeding.

In
`@pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.kt`:
- Around line 196-199: The test awaiting near-limit (~2MB) publishes in
LargeMessagePublishIntegrationTests.kt uses short await windows (await(seconds =
10) and another at lines referenced 218-221) which are brittle; increase these
timeout values (e.g., to 30s or 60s) for the await(...) calls in the failing
publish assertions so the lambda that checks
result.isFailure/result.getOrThrow().timetoken has a larger budget in
integration environments—update each await(...) invocation in
LargeMessagePublishIntegrationTests.kt that wraps the publish assertions
accordingly.
- Around line 95-123: The test registers an anonymous SubscribeCallback via
pubnub.addListener and subscribes with pubnub.subscribeToBlocking but never
removes the listener or cleans up the ad-hoc client/observer on the success
path, risking leaked callbacks and shared state; update the test to capture the
listener instance (instead of an anonymous object), ensure after
success.listen() you call pubnub.removeListener(theListener) and unsubscribe
(e.g., pubnub.unsubscribeFromBlocking or pubnub.unsubscribe) from
expectedChannel, and also shut down/close the observer/ad-hoc client (call its
close/stop/shutdown method) so both listener and client resources are always
torn down on the test path (do the same for the other test path around lines
205-222).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8da568ce-94ac-46e4-a6d4-888bbe885316

📥 Commits

Reviewing files that changed from the base of the PR and between 7d52742 and 099446c.

📒 Files selected for processing (4)
  • pubnub-gson/pubnub-gson-impl/src/integrationTest/java/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.java
  • pubnub-gson/pubnub-gson-impl/src/integrationTest/java/com/pubnub/api/integration/PublishIntegrationTests.java
  • pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/PublishIntegrationTests.kt
✅ Files skipped from review due to trivial changes (1)
  • pubnub-gson/pubnub-gson-impl/src/integrationTest/java/com/pubnub/api/integration/PublishIntegrationTests.java

Comment on lines +95 to +123
pubnub.addListener(
object : SubscribeCallback() {
override fun status(
pubnub: PubNub,
pnStatus: PNStatus,
) {
}

override fun message(
pubnub: PubNub,
event: PNMessageResult,
) {
if (event.message.asString == largeMessage) {
success.set(true)
}
}
},
)

pubnub.subscribeToBlocking(expectedChannel)

observer.publish(
message = largeMessage,
channel = expectedChannel,
usePost = true,
).sync()

success.listen()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Tear down listeners and ad-hoc clients after each test path.

The listener/client lifecycle isn’t closed in these paths, which can leak callbacks/resources and affect subsequent tests.

Suggested change
-        pubnub.addListener(
-            object : SubscribeCallback() {
+        val listener =
+            object : SubscribeCallback() {
                 override fun status(
                     pubnub: PubNub,
                     pnStatus: PNStatus,
                 ) {
                 }
@@
-            },
-        )
+            }
+        pubnub.addListener(listener)

-        pubnub.subscribeToBlocking(expectedChannel)
-
-        observer.publish(
-            message = largeMessage,
-            channel = expectedChannel,
-            usePost = true,
-        ).sync()
-
-        success.listen()
+        try {
+            pubnub.subscribeToBlocking(expectedChannel)
+            observer.publish(
+                message = largeMessage,
+                channel = expectedChannel,
+                usePost = true,
+            ).sync()
+            success.listen()
+        } finally {
+            pubnub.removeListener(listener)
+            pubnub.unsubscribeAll()
+            observer.destroy()
+        }
-        pubnubWithCrypto.publish(
-            channel = expectedChannel,
-            message = largeMessage,
-            usePost = true,
-        ).await(seconds = 15) { result ->
-            assertFalse(result.isFailure)
-            assertTrue(result.getOrThrow().timetoken > 0)
-        }
+        try {
+            pubnubWithCrypto.publish(
+                channel = expectedChannel,
+                message = largeMessage,
+                usePost = true,
+            ).await(seconds = 15) { result ->
+                assertFalse(result.isFailure)
+                assertTrue(result.getOrThrow().timetoken > 0)
+            }
+        } finally {
+            pubnubWithCrypto.destroy()
+        }

As per coding guidelines, pubnub-kotlin/pubnub-kotlin-impl/**: "Verify proper error handling and resource cleanup." and "Check for potential memory leaks in subscription/listener management.", and **/*Test*: "Ensure proper test isolation."

Also applies to: 205-222

🧰 Tools
🪛 detekt (1.23.8)

[warning] 100-101: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.kt`
around lines 95 - 123, The test registers an anonymous SubscribeCallback via
pubnub.addListener and subscribes with pubnub.subscribeToBlocking but never
removes the listener or cleans up the ad-hoc client/observer on the success
path, risking leaked callbacks and shared state; update the test to capture the
listener instance (instead of an anonymous object), ensure after
success.listen() you call pubnub.removeListener(theListener) and unsubscribe
(e.g., pubnub.unsubscribeFromBlocking or pubnub.unsubscribe) from
expectedChannel, and also shut down/close the observer/ad-hoc client (call its
close/stop/shutdown method) so both listener and client resources are always
torn down on the test path (do the same for the other test path around lines
205-222).

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.kt (2)

196-199: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Increase timeout budget for near-2MB publish tests.

Line 196 (10s) and Line 218 (15s) are tight for integration environments with large payloads and can intermittently fail. Use a larger budget (e.g., 60s) to reduce flakiness.

Suggested minimal fix
-        ).await(seconds = 10) { result ->
+        ).await(seconds = 60) { result ->
@@
-        ).await(seconds = 15) { result ->
+        ).await(seconds = 60) { result ->

As per coding guidelines, **/*Test*: "Check for flaky test patterns (timing, ordering dependencies)."

Also applies to: 218-221

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.kt`
around lines 196 - 199, The near-2MB publish integration tests in
LargeMessagePublishIntegrationTests use too-small await timeouts (the
await(seconds = 10) { ... } block and another await(seconds = 15) { ... } block)
which causes flakiness; increase these timeout budgets to a larger value (e.g.,
await(seconds = 60) { ... }) in both places where the publish result is asserted
(the lambda containing assertFalse(result.isFailure) and
assertTrue(result.getOrThrow().timetoken > 0)) so the integration environment
has enough time to complete large payload publishes.

95-123: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure listener/client resources are always torn down.

Line 95 adds a listener and Line 93/Line 205 create ad-hoc clients, but there is no guaranteed cleanup path. This can leak callbacks/clients and pollute later tests. Wrap both flows in try/finally and always removeListener, unsubscribeAll (or channel unsubscribe), and destroy().

Suggested minimal fix
-        pubnub.addListener(
-            object : SubscribeCallback() {
+        val listener =
+            object : SubscribeCallback() {
                 override fun status(
                     pubnub: PubNub,
                     pnStatus: PNStatus,
                 ) {
                 }
@@
-            },
-        )
-
-        pubnub.subscribeToBlocking(expectedChannel)
-
-        observer.publish(
-            message = largeMessage,
-            channel = expectedChannel,
-            usePost = true,
-        ).sync()
-
-        success.listen()
+            }
+        pubnub.addListener(listener)
+
+        try {
+            pubnub.subscribeToBlocking(expectedChannel)
+            observer.publish(
+                message = largeMessage,
+                channel = expectedChannel,
+                usePost = true,
+            ).sync()
+            success.listen()
+        } finally {
+            pubnub.removeListener(listener)
+            pubnub.unsubscribeAll()
+            observer.destroy()
+        }
@@
-        pubnubWithCrypto.publish(
-            channel = expectedChannel,
-            message = largeMessage,
-            usePost = true,
-        ).await(seconds = 15) { result ->
-            assertFalse(result.isFailure)
-            assertTrue(result.getOrThrow().timetoken > 0)
-        }
+        try {
+            pubnubWithCrypto.publish(
+                channel = expectedChannel,
+                message = largeMessage,
+                usePost = true,
+            ).await(seconds = 15) { result ->
+                assertFalse(result.isFailure)
+                assertTrue(result.getOrThrow().timetoken > 0)
+            }
+        } finally {
+            pubnubWithCrypto.destroy()
+        }

As per coding guidelines, pubnub-kotlin/pubnub-kotlin-impl/**: "Verify proper error handling and resource cleanup." and "Check for potential memory leaks in subscription/listener management.", and **/*Test*: "Ensure proper test isolation."

Also applies to: 205-222

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.kt`
around lines 95 - 123, Wrap the subscription/publish flows in try/finally to
guarantee teardown: after registering the anonymous SubscribeCallback via
pubnub.addListener(...) and calling pubnub.subscribeToBlocking(...), ensure in a
finally block you call pubnub.removeListener(the same SubscribeCallback),
pubnub.unsubscribeAll() or pubnub.unsubscribe(expectedChannel), and
pubnub.destroy() for any ad-hoc PubNub clients created (the ones around lines
creating observer and other ad-hoc clients); similarly ensure any
observer/client created for publish has its destroy() invoked in finally. Use
the same SubscribeCallback reference (not an anonymous inline only) so
removeListener can be called, and perform unsubscribe/destroy in finally to
avoid leaking listeners/clients across tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.kt`:
- Around line 196-199: The near-2MB publish integration tests in
LargeMessagePublishIntegrationTests use too-small await timeouts (the
await(seconds = 10) { ... } block and another await(seconds = 15) { ... } block)
which causes flakiness; increase these timeout budgets to a larger value (e.g.,
await(seconds = 60) { ... }) in both places where the publish result is asserted
(the lambda containing assertFalse(result.isFailure) and
assertTrue(result.getOrThrow().timetoken > 0)) so the integration environment
has enough time to complete large payload publishes.
- Around line 95-123: Wrap the subscription/publish flows in try/finally to
guarantee teardown: after registering the anonymous SubscribeCallback via
pubnub.addListener(...) and calling pubnub.subscribeToBlocking(...), ensure in a
finally block you call pubnub.removeListener(the same SubscribeCallback),
pubnub.unsubscribeAll() or pubnub.unsubscribe(expectedChannel), and
pubnub.destroy() for any ad-hoc PubNub clients created (the ones around lines
creating observer and other ad-hoc clients); similarly ensure any
observer/client created for publish has its destroy() invoked in finally. Use
the same SubscribeCallback reference (not an anonymous inline only) so
removeListener can be called, and perform unsubscribe/destroy in finally to
avoid leaking listeners/clients across tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5dfdfabf-c6bc-4ab4-a784-94eef822332d

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2edb4 and b93866c.

📒 Files selected for processing (4)
  • pubnub-gson/pubnub-gson-impl/src/integrationTest/java/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.java
  • pubnub-kotlin/pubnub-kotlin-impl/src/integrationTest/kotlin/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/api/PubNubUtilTest.kt
  • pubnub-kotlin/pubnub-kotlin-impl/src/test/kotlin/com/pubnub/internal/suite/pubsub/PublishV2PostMapPayloadTestSuite.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • pubnub-gson/pubnub-gson-impl/src/integrationTest/java/com/pubnub/api/integration/LargeMessagePublishIntegrationTests.java

…ation for messages send via Publish, Signal and received messages and signals in subscribe.

Added config property loggedMessageContentMaxBytes that limits size of message content logged.

Added fix limit 24KB for http response message being logged.

Added logging for received message and signals.
…ation for publishFileMessage

Added config property loggedMessageContentMaxBytes that limits size of message content logged.
…ontentMaxBytes and new property loggedHttpResponseMaxBytes having default 2000
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