Skip to content

SDKS-4249 QA for new DaVinci collectors#204

Open
spetrov wants to merge 11 commits into
developfrom
SDKS-4249
Open

SDKS-4249 QA for new DaVinci collectors#204
spetrov wants to merge 11 commits into
developfrom
SDKS-4249

Conversation

@spetrov
Copy link
Copy Markdown
Contributor

@spetrov spetrov commented May 26, 2026

JIRA Ticket

SDKS-4249 [QA] Support for new DaVinci collectors

Description

New tests added in FormFieldsTest.kt, FormFieldValidationTest.kt, and PollingCollectorE2ETests.kt covering the following collectors/features:

  • ReadOnlyTextCollector (agreement text)
  • BooleanCollector (required "I agree" checkbox)
  • PhoneNumberCollector (object payload format, extension field, end-to-end submission)
  • LabelCollector (multiple rich text links, target field for open-in-new-tab)
  • PollingCollector (timeout, completion, challenge approval, QR code challenge)
  • Password policy validation (max repeat, max length, field-level policy source)

Summary by CodeRabbit

  • New Features

    • Polling collector supports OOB and QR-code approval workflows with timeout handling
    • Rich-text labels support multiple links with configurable targets
    • Phone-number collector accepts object format (country code, number, optional extension)
    • Agreement collector for checkbox-based consent flows
    • Enhanced password validation (max repeated characters, max length) and policy metadata surfaced
  • Tests

    • Expanded E2E and instrumentation coverage for polling (including QR approval), password policies, phone formats, agreement flows, and rich-text link behavior
    • Added a comprehensive polling test flow configuration used by tests
  • Chores

    • Added QR-code decoding test dependency and pinned JaCoCo test coverage version

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 86509889-ce95-4bd9-80ba-c9d0fd13d3f9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds ZXing test dependency, a large polling flow JSON and end-to-end polling tests (including QR approval), expands password-policy validation tests, extends form-field instrumentation tests (multi-link labels, phone object format, agreement flow), and pins JaCoCo version for test coverage.

Changes

Polling and Form Field Test Coverage

Layer / File(s) Summary
Polling Foundation: Dependency and Flow Configuration
davinci/build.gradle.kts, davinci/src/androidTest/kotlin/com/pingidentity/davinci/Polling-Collector-Tests-Flows.json
Adds com.google.zxing:core:3.5.3 and a 2675-line polling flow JSON (metadata, graph nodes/edges, forms) for continue/challenge/QR-code polling scenarios.
Polling Collector E2E Tests: Test Class, Setup, and Scenarios
davinci/src/androidTest/kotlin/com/pingidentity/davinci/PollingCollectorE2ETests.kt
Adds PollingCollectorE2ETests with @BeforeTest logout, multiple polling scenarios (continue timeout/finish, challenge timeout/approval, QR timeout/approval), concurrent delayed approval via HTTP GET, and a ZXing QR decode helper.
Password Field Validation Tests: Rules, Metadata, and Field-Level Sourcing
davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt
Adds imports and five tests: MaxRepeat, max-length InvalidLength, clear-on-close, additional password policy metadata checks, and verification that passwordPolicy is embedded at the PASSWORD_VERIFY field. Adjusts an error-node collector index and OIDC test acrValues.
Form Field Collector Tests: Labels, Phone, and Agreement
davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldsTest.kt
Updates imports and companion constants, changes OIDC test config, expands rich-text label expectations to two links and per-link targets, adds phone-number object-format parsing/payload/extension tests, replaces booleanCollectorTest with agreementCollectorTest, and adds phone submission tests and a fillRequiredFields helper.
Build tooling: JaCoCo version and properties
build-logic/convention/src/main/kotlin/com/pingidentity/convention/AndroidLibraryConventionPlugin.kt, gradle.properties
Pins JaCoCo to 0.8.13 for test coverage instrumentation; gradle.properties line repositioned without content change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested reviewers

  • witrisna
  • vibhorgoswami

Poem

🐰 I sniffed a QR and found a string,
Polls hummed on loops until a bell would ring,
Password rules counted repeats and length,
Links and phones reported payloads at length,
JaCoCo pinned — the tests take wing!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.67% 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
Title check ✅ Passed The title clearly identifies the main purpose as QA/testing for new DaVinci collectors, which aligns with the changeset's primary focus on adding comprehensive test coverage.
Description check ✅ Passed The description includes the required JIRA ticket link and a comprehensive description of the changes with clear enumeration of tested collectors and features.
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.

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


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

Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt (1)

2-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update modified-file copyright year range.

Line 2 should use the modified-file range format (e.g., 2025-2026) per repo rule.

Based on learnings, existing Kotlin/Java files modified in this repo should use a year range from original year through current year.

🤖 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
`@davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt`
at line 2, Update the copyright header in FormFieldValidationTest.kt to use the
modified-file year range format (e.g., replace the single year "2025" on line 2
with a range like "2025-2026"); locate the top-of-file copyright comment in
FormFieldValidationTest.kt and change the year to span from the original
copyright year through the current year per repo rules.
🤖 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 `@davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldsTest.kt`:
- Around line 714-735: The test
phoneNumberSubmissionWithEmptyPhoneOmitsFieldTest currently never advances past
setting the submit value, so it only checks local collector behavior; to
actually exercise the POST/server path call node = node.next() after setting
(node.collectors[SUBMIT_BUTTON_INDEX] as SubmitCollector).value = "Submit" to
trigger submission and then assert the server-side validation response (or,
alternatively, rename the test and its comments to indicate it only verifies
local validate()/payload() behavior); update assertions accordingly to either
expect a server error after advancing the node or keep the existing local
assertions if you rename/scope the test.

In
`@davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt`:
- Around line 32-43: Remove the duplicate import of assertFalse in
FormFieldValidationTest.kt so only one assertion API is used; specifically
delete the import line referencing junit.framework.TestCase.assertFalse (leave
kotlin.test.assertFalse) and ensure all uses of assertFalse in the file remain
unqualified to consistently resolve to kotlin.test.assertFalse throughout the
test class.

In
`@davinci/src/androidTest/kotlin/com/pingidentity/davinci/PollingCollectorE2ETests.kt`:
- Around line 43-44: The test class PollingCollectorE2ETests is incorrectly
marked `@SmallTest`; change the annotation to `@LargeTest` to reflect network-backed
E2E behavior, and in the approval/polling flow replace any direct
URL.openStream() calls with an HttpURLConnection (or URLConnection) where you
set connect and read timeouts (setConnectTimeout/setReadTimeout) and avoid
unbounded Thread.sleep/fixed delays by implementing a polling strategy with
bounded overall timeout (e.g., total deadline + retry/backoff loop or
exponential backoff) so the tests cannot hang indefinitely.
- Around line 236-244: Replace the fragile fixed-delay approval in
challengePollingApproval and qrCodeChallengePollingApproval by triggering the
approval when a poll event is observed: subscribe to
pollingCollector.pollStatus() and, upon receiving the first (or appropriate)
emitted status, call the existing openApprovalUrl(...) helper to perform the
approval; remove the hard-coded delay(3000L) and ensure the approval coroutine
runs only after observing the poll emission. Also modify openApprovalUrl(...)
(or the URL-opening path used by the tests) to use a URLConnection with explicit
connect and read timeouts (non-zero) so the approval HTTP request cannot hang
indefinitely. Ensure references: pollingCollector.pollStatus(),
challengePollingApproval, qrCodeChallengePollingApproval, and
openApprovalUrl(...) are updated accordingly.

---

Outside diff comments:
In
`@davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt`:
- Line 2: Update the copyright header in FormFieldValidationTest.kt to use the
modified-file year range format (e.g., replace the single year "2025" on line 2
with a range like "2025-2026"); locate the top-of-file copyright comment in
FormFieldValidationTest.kt and change the year to span from the original
copyright year through the current year per repo rules.
🪄 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: b7883a31-9422-4c66-8ff0-ac5b2497ad75

📥 Commits

Reviewing files that changed from the base of the PR and between 02ca67e and 994ed5a.

📒 Files selected for processing (5)
  • davinci/build.gradle.kts
  • davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt
  • davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldsTest.kt
  • davinci/src/androidTest/kotlin/com/pingidentity/davinci/Polling-Collector-Tests-Flows.json
  • davinci/src/androidTest/kotlin/com/pingidentity/davinci/PollingCollectorE2ETests.kt

Comment thread davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldsTest.kt Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 44.94%. Comparing base (02ca67e) to head (f043107).

Additional details and impacted files
@@              Coverage Diff              @@
##             develop     #204      +/-   ##
=============================================
+ Coverage      42.64%   44.94%   +2.29%     
- Complexity      1287     1389     +102     
=============================================
  Files            312      312              
  Lines           9447     9465      +18     
  Branches        1403     1429      +26     
=============================================
+ Hits            4029     4254     +225     
+ Misses          4862     4650     -212     
- Partials         556      561       +5     
Flag Coverage Δ
integration-tests 29.20% <ø> (+4.12%) ⬆️
unit-tests 26.34% <ø> (+0.44%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
build-logic/convention/src/main/kotlin/com/pingidentity/convention/JacocoPlugin.kt (1)

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

Update copyright year range for a modified Kotlin file.

Line 2 should include the current year (2026) for a modified existing file; otherwise this violates repository copyright-header compliance.

Proposed fix
- * Copyright (c) 2024 - 2025 Ping Identity Corporation. All rights reserved.
+ * Copyright (c) 2024 - 2026 Ping Identity Corporation. All rights reserved.

Based on learnings: In the ForgeRock/ping-android-sdk repository, existing modified Kotlin/Java files must use a creation-year-through-current-year range.

🤖 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
`@build-logic/convention/src/main/kotlin/com/pingidentity/convention/JacocoPlugin.kt`
at line 2, Update the copyright header in JacocoPlugin.kt to include the current
year (2026) by changing the creation-year range to end with 2026 (e.g., "2024 -
2026") so the modified Kotlin file complies with repository copyright-header
rules; locate the top-of-file comment in
build-logic/convention/src/main/kotlin/com/pingidentity/convention/JacocoPlugin.kt
and replace the existing year range on the second line accordingly.
🤖 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.

Outside diff comments:
In
`@build-logic/convention/src/main/kotlin/com/pingidentity/convention/JacocoPlugin.kt`:
- Line 2: Update the copyright header in JacocoPlugin.kt to include the current
year (2026) by changing the creation-year range to end with 2026 (e.g., "2024 -
2026") so the modified Kotlin file complies with repository copyright-header
rules; locate the top-of-file comment in
build-logic/convention/src/main/kotlin/com/pingidentity/convention/JacocoPlugin.kt
and replace the existing year range on the second line accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aed51049-029e-4b58-9c17-07526b0e6df7

📥 Commits

Reviewing files that changed from the base of the PR and between 994ed5a and e90eb75.

📒 Files selected for processing (1)
  • build-logic/convention/src/main/kotlin/com/pingidentity/convention/JacocoPlugin.kt

Copy link
Copy Markdown
Contributor

@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: 2

🤖 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
`@davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt`:
- Around line 310-313: The two assertions use the wrong argument order and call
.toString()/!! which can throw NPEs before the assertion; change them to call
assertNotNull with the actual nullable values first and the message second, e.g.
assertNotNull(passwordField, "PASSWORD_VERIFY field not found in form
components") and assertNotNull(passwordField?.get("passwordPolicy"),
"passwordPolicy must be embedded inside the PASSWORD_VERIFY field") (remove
.toString() and !! so you assert the raw nullable values instead of masking
nulls).

In
`@davinci/src/androidTest/kotlin/com/pingidentity/davinci/PollingCollectorE2ETests.kt`:
- Around line 247-253: The test can hang because firstContinueSeen.await() (and
similarly approvalJob.await()) may never complete; wrap these awaits in a
coroutine timeout (e.g., use kotlinx.coroutines.withTimeout or
withTimeoutOrNull) so the test fails fast instead of hanging. Locate the await
calls (firstContinueSeen.await() and the approvalJob.await() inside the
magic-link and QR-code approval flows) and replace them with a timed variant
(for example: withTimeout(30_000) { firstContinueSeen.await() } or use
withTimeoutOrNull and assert null as a failure), ensuring you import
kotlinx.coroutines.withTimeout/withTimeoutOrNull.
🪄 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: f7bd7075-c514-47dc-944b-acd2e64481b7

📥 Commits

Reviewing files that changed from the base of the PR and between e90eb75 and f15548b.

📒 Files selected for processing (4)
  • davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt
  • davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldsTest.kt
  • davinci/src/androidTest/kotlin/com/pingidentity/davinci/PollingCollectorE2ETests.kt
  • gradle.properties
✅ Files skipped from review due to trivial changes (1)
  • gradle.properties

Comment on lines +310 to +313
assertNotNull("PASSWORD_VERIFY field not found in form components", passwordField.toString())
assertNotNull(
"passwordPolicy must be embedded inside the PASSWORD_VERIFY field",
passwordField!!["passwordPolicy"].toString())
Copy link
Copy Markdown
Contributor

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt"

echo "Checking for message-first assertNotNull usage:"
rg -n 'assertNotNull\(\s*"[^"]+"\s*,\s*' "$FILE"

echo
echo "Checking for toString()-based null assertions:"
rg -n 'assertNotNull\([^)]*toString\(\)' "$FILE"

Repository: ForgeRock/ping-android-sdk

Length of output: 378


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt"
nl -ba "$FILE" | sed -n '290,340p'

Repository: ForgeRock/ping-android-sdk

Length of output: 113


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt"

awk 'NR>=290 && NR<=340 {printf "%d:%s\n", NR, $0}' "$FILE"

Repository: ForgeRock/ping-android-sdk

Length of output: 2428


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt"
awk 'NR<=80{printf "%d:%s\n", NR, $0}' "$FILE"

Repository: ForgeRock/ping-android-sdk

Length of output: 3262


Fix assertNotNull argument order and remove null-masking .toString()/!!

In FormFieldValidationTest.kt lines 310-313, kotlin.test.assertNotNull is called as assertNotNull("message", <value>.toString()), so the non-null message string is used as the actual value (the assertion can pass even when passwordField/passwordPolicy are null). Also, passwordField.toString() / passwordField!!["passwordPolicy"].toString() can throw NPE before the assertion message is surfaced.

🤖 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
`@davinci/src/androidTest/kotlin/com/pingidentity/davinci/FormFieldValidationTest.kt`
around lines 310 - 313, The two assertions use the wrong argument order and call
.toString()/!! which can throw NPEs before the assertion; change them to call
assertNotNull with the actual nullable values first and the message second, e.g.
assertNotNull(passwordField, "PASSWORD_VERIFY field not found in form
components") and assertNotNull(passwordField?.get("passwordPolicy"),
"passwordPolicy must be embedded inside the PASSWORD_VERIFY field") (remove
.toString() and !! so you assert the raw nullable values instead of masking
nulls).

Comment on lines +247 to +253
firstContinueSeen.await()
(java.net.URL(magicLink).openConnection() as java.net.HttpURLConnection).apply {
connectTimeout = 10_000
readTimeout = 10_000
connect()
disconnect()
}
Copy link
Copy Markdown
Contributor

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="davinci/src/androidTest/kotlin/com/pingidentity/davinci/PollingCollectorE2ETests.kt"

echo "Inspect approval await blocks:"
nl -ba "$FILE" | sed -n '236,255p'
nl -ba "$FILE" | sed -n '360,373p'

echo
echo "Check for timeout-guarded awaits:"
rg -n 'firstContinueSeen\.await\(\)|withTimeout\(' "$FILE"

Repository: ForgeRock/ping-android-sdk

Length of output: 162


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="davinci/src/androidTest/kotlin/com/pingidentity/davinci/PollingCollectorE2ETests.kt"

echo "=== Context around lines ~240-260 ==="
sed -n '230,270p' "$FILE" | awk '{
  # best-effort: annotate with approximate line numbers using NR + (start-1)
}' 

echo "=== Context around lines ~350-390 ==="
sed -n '350,390p' "$FILE"

echo
echo "=== All occurrences of firstContinueSeen.await() in the file ==="
rg -n --no-heading 'firstContinueSeen\.await\(\)' "$FILE" || true

echo
echo "=== Any existing timeout guard usage in the file ==="
rg -n --no-heading 'withTimeout\(|Timeout' "$FILE" || true

Repository: ForgeRock/ping-android-sdk

Length of output: 2577


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="davinci/src/androidTest/kotlin/com/pingidentity/davinci/PollingCollectorE2ETests.kt"

echo "=== File header/context (top ~1-120) ==="
sed -n '1,120p' "$FILE"

echo
echo "=== Tests/helpers with polling timeouts (~120-220) ==="
sed -n '120,220p' "$FILE"

echo
echo "=== Context around firstContinueSeen.await() `#1` (~220-270) ==="
sed -n '220,270p' "$FILE"

echo
echo "=== Context around firstContinueSeen.await() `#2` (~330-390) ==="
sed -n '330,390p' "$FILE"

Repository: ForgeRock/ping-android-sdk

Length of output: 15782


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate PollingCollector implementation + PollingStatus definitions
rg -n --no-heading "class PollingCollector|fun pollStatus\(" -S davinci/src main/src test/src androidTest/src . || true

# Narrow to davinci collector package
rg -n --no-heading "class PollingCollector|fun pollStatus\(|sealed class PollingStatus|class .*PollingStatus|enum class .*PollingStatus" -S davinci/src . || true

# List files that look relevant
fd -t f -e kt -e kts -e java "PollingCollector" davinci/src . 2>/dev/null || true
fd -t f -e kt -e kts -e java "PollingStatus" davinci/src . 2>/dev/null || true

Repository: ForgeRock/ping-android-sdk

Length of output: 4028


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="davinci/src/main/kotlin/com/pingidentity/davinci/collector/PollingCollector.kt"

echo "=== File length ==="
wc -l "$FILE"

echo
echo "=== PollingStatus definitions (~40-110) ==="
sed -n '40,120p' "$FILE"

echo
echo "=== PollingCollector.pollStatus() (~260-380) ==="
sed -n '260,380p' "$FILE"

echo
echo "=== Any mention of TimedOut / retry logic near pollStatus() (~200-320) ==="
rg -n --no-heading "TimedOut|retry|maxRetries|pollRetries|pollInterval|delay|timeout" "$FILE" | head -n 80

Repository: ForgeRock/ping-android-sdk

Length of output: 13057


Add a timeout around firstContinueSeen.await() to prevent CI hangs

PollingCollector.pollStatus() can emit Expired, Error, or Complete without ever emitting the first PollingStatus.Continue; in that case approvalJob.await() will never complete (both the magic-link approval at lines 247-253 and the QR-code approval at 366-372).

🔧 Minimal hardening diff
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
 import kotlinx.coroutines.withContext
@@
         val approvalJob = async(Dispatchers.IO) {
-            firstContinueSeen.await()
+            withTimeout(20_000L) { firstContinueSeen.await() }
             (java.net.URL(magicLink).openConnection() as java.net.HttpURLConnection).apply {
                 connectTimeout = 10_000
                 readTimeout = 10_000
                 connect()
                 disconnect()
             }
         }
@@
         val approvalJob = async(Dispatchers.IO) {
-            firstContinueSeen.await()
+            withTimeout(20_000L) { firstContinueSeen.await() }
             (java.net.URL(approvalUrl).openConnection() as java.net.HttpURLConnection).apply {
                 connectTimeout = 10_000
                 readTimeout = 10_000
                 connect()
                 disconnect()
             }
         }
🤖 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
`@davinci/src/androidTest/kotlin/com/pingidentity/davinci/PollingCollectorE2ETests.kt`
around lines 247 - 253, The test can hang because firstContinueSeen.await() (and
similarly approvalJob.await()) may never complete; wrap these awaits in a
coroutine timeout (e.g., use kotlinx.coroutines.withTimeout or
withTimeoutOrNull) so the test fails fast instead of hanging. Locate the await
calls (firstContinueSeen.await() and the approvalJob.await() inside the
magic-link and QR-code approval flows) and replace them with a timed variant
(for example: withTimeout(30_000) { firstContinueSeen.await() } or use
withTimeoutOrNull and assert null as a failure), ensuring you import
kotlinx.coroutines.withTimeout/withTimeoutOrNull.

Copy link
Copy Markdown
Contributor

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
build-logic/convention/src/main/kotlin/com/pingidentity/convention/AndroidLibraryConventionPlugin.kt (1)

2-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update copyright year to include 2026.

This file is being modified in 2026, so the copyright year range should extend through the current year. Based on learnings, modified files should use a year range from the original creation year through the current year.

📅 Proposed fix
- * Copyright (c) 2024 - 2025 Ping Identity Corporation. All rights reserved.
+ * Copyright (c) 2024 - 2026 Ping Identity Corporation. All rights reserved.
🤖 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
`@build-logic/convention/src/main/kotlin/com/pingidentity/convention/AndroidLibraryConventionPlugin.kt`
at line 2, Update the copyright header in AndroidLibraryConventionPlugin.kt by
extending the year range to include 2026: locate the file-level comment line
that currently reads "Copyright (c) 2024 - 2025 Ping Identity Corporation. All
rights reserved." and change the end year to 2026 so it reads through 2026;
ensure you only modify that header text and do not alter the
AndroidLibraryConventionPlugin class or other code.
🤖 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.

Outside diff comments:
In
`@build-logic/convention/src/main/kotlin/com/pingidentity/convention/AndroidLibraryConventionPlugin.kt`:
- Line 2: Update the copyright header in AndroidLibraryConventionPlugin.kt by
extending the year range to include 2026: locate the file-level comment line
that currently reads "Copyright (c) 2024 - 2025 Ping Identity Corporation. All
rights reserved." and change the end year to 2026 so it reads through 2026;
ensure you only modify that header text and do not alter the
AndroidLibraryConventionPlugin class or other code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 31e8f57d-c753-4aeb-9a20-d4b195147b71

📥 Commits

Reviewing files that changed from the base of the PR and between f15548b and 17235aa.

📒 Files selected for processing (2)
  • build-logic/convention/src/main/kotlin/com/pingidentity/convention/AndroidLibraryConventionPlugin.kt
  • gradle.properties
💤 Files with no reviewable changes (1)
  • gradle.properties

namespace = "com.pingidentity.${project.name.replace("-", ".")}"
// bcprov-jdk18on 1.83+ contains Java 25 multi-release class files (major version 69).
// AGP's default JaCoCo 0.8.12 (ASM 9.7) cannot instrument them; 0.8.13 (ASM 9.8) can.
testCoverage { jacocoVersion = "0.8.13" }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PR #201 broke the pipeline. This fixes it...

clientId = "021b83ce-a9b1-4ad4-8c1d-79e576eeab76"
discoveryEndpoint = "https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/.well-known/openid-configuration"
clientId = "a6859a12-5e6e-4f64-96bb-cc8577706bee"
discoveryEndpoint = "https://auth.pingone.ca/300c4f2a-39d4-4ba9-a18a-f6de246006f4/as/.well-known/openid-configuration"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the new shared environment dedicated to QA e2e tests... We are gonna gradually switch to this one...

}

@Test
fun passwordClearPasswordAfterCloseTest() = runTest {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Incorrect assertNotNull signature — assertion message and value are swapped

assertNotNull("PASSWORD_VERIFY field not found in form components", passwordField.toString())
assertNotNull("passwordPolicy must be embedded inside the PASSWORD_VERIFY field", passwordField!!["passwordPolicy"].toString())

kotlin.test.assertNotNull(message, actual) was deprecated — the two-arg overload is assertNotNull(actual, message). As written, the string literal is always non-null, so this assertion never fails regardless of whether passwordField is actually null.

Also, calling .toString() on a nullable before the null-check defeats the purpose entirely — null.toString() returns "null" (a non-null string), so the assertion passes even when the value is missing.

Fix:

assertNotNull(passwordField, "PASSWORD_VERIFY field not found in form components")
assertNotNull(passwordField!!["passwordPolicy"], "passwordPolicy must be embedded inside the PASSWORD_VERIFY field")

Comment thread davinci/build.gradle.kts
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(project(":foundation:testrail"))
androidTestImplementation(project(":protect"))
androidTestImplementation("com.google.zxing:core:3.5.3")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ZXing dependency not version-catalogued

androidTestImplementation("com.google.zxing:core:3.5.3")

Every other dependency in this file uses the libs.* version catalog alias (e.g. libs.androidx.test.runner). Adding a hard-coded GAV string bypasses the catalog, making it invisible to dependencyUpdates checks and inconsistent with the project style.

Add zxing-core = { module = "com.google.zxing:core", version = "3.5.3" } to gradle/libs.versions.toml and reference it as libs.zxing.core here.

import com.pingidentity.davinci.plugin.collectors
import com.pingidentity.logger.Logger
import com.pingidentity.logger.STANDARD
import com.pingidentity.orchestrate.ContinueNode
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

runBlocking + withContext(Dispatchers.IO) in tests instead of runTest

challengePollingTimeout, challengePollingApproval, qrCodeChallengePollingTimeout, and qrCodeChallengePollingApproval all use runBlocking with explicit withContext(Dispatchers.IO) wrappers. The simpler polling tests use runTest directly.

runTest is the preferred coroutine test runner; it integrates with TestCoroutineScheduler, supports virtual time, and avoids blocking the test thread. Using runBlocking forces real-time waits and makes the tests slower than necessary.

If the reason is that pollStatus() involves real network I/O that cannot be faked out, that is fine — but the pattern should at least be documented here so future maintainers do not wonder why the inconsistency exists.


// Cycle 1 — retriesAllowed 3→2, emits Complete("continue"); server rewinds to same form
var statuses = pollingCollector.pollStatus().toList()
assertEquals(1, statuses.size)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

continuePollingTimeout reuses the same pollingCollector instance across cycles

var node = node.next() as ContinueNode  // server rewinds to same form
// ...
statuses = pollingCollector.pollStatus().toList()  // called on the old collector

After node.next(), the server returns a fresh ContinueNode — but the test continues calling pollStatus() on the pollingCollector extracted from the previous node. If the SDK returns a new collector instance for the rewound form, the retriesAllowed counter may be double-counted or the state may be stale.

The test should re-extract the collector after each node.next():

node = node.next() as ContinueNode
val pollingCollector = node.collectors.filterIsInstance<PollingCollector>().first()

Or document explicitly that the same collector instance is intentionally shared across cycles (if the SDK guarantees this).

node = node.next() as ContinueNode

assertEquals("Automation - Polling", node.name)
val pollingCollector = node.collectors.filterIsInstance<PollingCollector>().first()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

pollInterval and pollRetries are stringly-typed comparisons

assertEquals("2000", pollingCollector.pollInterval)
assertEquals("3", pollingCollector.pollRetries)

If pollInterval and pollRetries are exposed as String properties it is better to either:

  • expose them as Int / Long in the SDK and compare numerically, or
  • at least assert pollingCollector.pollInterval.toInt() == 2000 so the assertion message shows a meaningful value mismatch rather than a string difference.

Also worth noting: continuePollingTimeout asserts these as strings, but challengePollingApproval uses pollingCollector.pollInterval.toInt() > 0 — the inconsistency suggests the string API is an accident rather than a design choice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants