SDKS-5065 Standardize SDK configuration#207
Conversation
Replace the sample app's hardcoded-presets + 30-key DataStore config model with a standardized JSON-file-driven configuration that mirrors the iOS sibling implementation. Introduces a unified Configuration data class, ConfigType enum, ConfigurationParser with validation, and a ConfigurationManager singleton backed by StateFlow and DataStore. EnvViewModel shrinks from ~600 to ~30 lines. All 9 consumer call sites updated. 29 unit tests added. Phases: - Phase 1: bundle assets/ping_sdk_config.json + kotlinx-serialization plugin - Phase 2: Configuration model + ConfigurationParser + 18 unit tests - Phase 3: ConfigurationManager + DataStore persistence + SDK builders - Phase 4: replace EnvViewModel, rewire Compose UI, update all call sites Refs: SDKS-5065
📝 WalkthroughWalkthroughThis PR standardizes SDK configuration in the Ping sample app by introducing a centralized ChangesSample App Configuration System Refactoring
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #207 +/- ##
==========================================
Coverage 42.16% 42.16%
Complexity 1287 1287
==========================================
Files 315 315
Lines 9605 9605
Branches 1447 1447
==========================================
Hits 4050 4050
Misses 4999 4999
Partials 556 556
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/userprofile/UserProfileViewModel.kt (1)
90-103:⚠️ Potential issue | 🟠 Major | ⚡ Quick winClear per-tab state when the configured client is missing.
When
ConfigurationManager.*isnull, these blocks do nothing and keep old user/error values, so the UI can show stale profile data after config/session changes. Please reset that tab’s state before returning when client/user is unavailable.Suggested fix
private fun journeyUserInfo() { viewModelScope.launch { - ConfigurationManager.journey?.journeyUser()?.let { user -> - when (val result = user.userinfo(false)) { - is Result.Failure -> - state.update { s -> - s.copy(journeyUser = null, journeyError = result.value) - } - - is Result.Success -> { - state.update { s -> - s.copy(journeyUser = result.value, journeyError = null) - } - } - } - } + val user = ConfigurationManager.journey?.journeyUser() + if (user == null) { + state.update { s -> s.copy(journeyUser = null, journeyError = null) } + return@launch + } + when (val result = user.userinfo(false)) { + is Result.Failure -> + state.update { s -> + s.copy(journeyUser = null, journeyError = result.value) + } + is Result.Success -> + state.update { s -> + s.copy(journeyUser = result.value, journeyError = null) + } + } } }Apply the same pattern to
daVinciUserInfo()andoidcUserInfo().Also applies to: 116-129, 142-155
🤖 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 `@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/userprofile/UserProfileViewModel.kt` around lines 90 - 103, When ConfigurationManager.journey (and similarly ConfigurationManager.daVinci and ConfigurationManager.oidc) is null the existing branches do nothing and leave stale state; update the null-case to explicitly reset that tab’s state by calling state.update to set the corresponding user field (journeyUser/daVinciUser/oidcUser) to null and the corresponding error field (journeyError/daVinciError/oidcError) to a cleared value before returning. Locate the blocks using ConfigurationManager.journey?.journeyUser()?.let { user -> ... } and the daVinciUserInfo()/oidcUserInfo() usages and add the fallback state.update calls mirroring the success/failure resets so each tab is cleared when its client/user is unavailable.
🤖 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 `@CHANGELOG.md`:
- Line 2: The changelog has a heading level jump: replace the "#### Changed"
heading under "## [Unreleased]" with "### Changed" so the heading increments by
one level and satisfies MD001; update the heading text where "#### Changed"
appears in CHANGELOG.md to "### Changed".
In
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Configuration.kt`:
- Around line 1-6: Update the copyright header in the new file Configuration.kt
to use only the current year (2026) instead of a range; replace "2024 - 2026"
with "2026" in the file header comment so it reads "Copyright (c) 2026 Ping
Identity Corporation".
In
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationDefaults.kt`:
- Line 65: ConfigurationDefaults.isDefault currently only checks
Configuration.name which lets a user config reuse a bundled default name and be
misclassified; update the logic so default/user identity is based on a stronger
key (e.g., check both Configuration.type and Configuration.name in
ConfigurationDefaults.isDefault or add a persisted origin/source flag on
Configuration and make isDefault consult that), and ensure ConfigurationParser
rejects user-provided configs whose (type, name) collide with any bundled
default; also update Env.kt UI checks (currently relying on isDefault) and
ConfigurationManager.add/update/delete to use the new identity so edit/delete
behavior is correct.
In
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.kt`:
- Around line 185-215: The add(Configuration) and update(oldName, Configuration)
paths currently write userConfigs directly to _configurations and
writeUserConfigs without validating or enforcing uniqueness, risking duplicates
or invalid entries that break deserializeUserConfigs; before modifying state in
add and before mapping/replacing in update, run the same validation and
uniqueness checks used by ConfigurationParser (validate structure, required
fields, and that names are unique among userConfigs and do not collide with
ConfigurationDefaults), reject or normalize invalid configs, and only
persist/writeUserConfigs and update _configurations/_selections (and call
rebuildInstance) when validation passes.
- Around line 1-6: The file header uses a ranged copyright year; update the
header in ConfigurationManager.kt to use the single current year "2026" instead
of "2024 - 2026" — locate the top-of-file comment block (the file-level header
above the ConfigurationManager class) and replace the ranged year with the
single year 2026 so the new-file header matches the repo convention.
- Around line 236-248: When removing a selection because no fallback exists,
also clear/tear down the active SDK client so the app doesn't keep using the
deleted config; in the else branch where you call
newSelections.remove(config.type) and update _selections.value, invoke the same
teardown used elsewhere (e.g. call rebuildInstance(null) or the client-destroy
method used by rebuildInstance) and persist the removal via writeSelection if
applicable (use appContext?.let { writeSelection(it, config.type, null) } or
equivalent) so the live client is cleared when fallback == null.
In
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Env.kt`:
- Around line 142-145: openAdd currently resets sheetFormState to a plain
ConfigFormState() which defaults the form to JOURNEY; change it to seed the new
form with the card's actual ConfigType so the modal opens with the right type.
Specifically, in openAdd (and the other openAdd occurrences referenced), keep
setting editingOldName = AddMode but initialize sheetFormState with the card's
ConfigType (e.g., new ConfigFormState(configType = currentCard.configType) or
equivalent) so the Add sheet defaults to the invoking card's type rather than
JOURNEY.
- Around line 156-163: canSave and onSave diverge from the real configuration
contract and allow/persist values ConfigurationParser would reject; update
canSave to reuse the same validation logic as ConfigurationParser (e.g., call
the parser's validate/parse method or a shared isValidConfiguration function) to
enforce non-empty scopes, well-formed redirectUri/discoveryEndpoint, and
required Journey fields (cookieName unless parser allows omission, and allow
omission of realm if parser does), and modify onSave to run the same
parser/validator before envViewModel.add or envViewModel.update (reject/save
only if parser returns valid Configuration or convert form via the parser) so
the UI cannot persist configurations the SDK builders would reject; reference
ConfigFormState.toConfiguration(), ConfigurationParser, canSave, onSave, and
envViewModel.add/update when making the changes.
- Around line 156-163: Reject saving when the new Configuration.name would
collide with any existing user configuration or a bundled default: in onSave
(use ConfigFormState.toConfiguration() and editingOldName) validate the
candidate name before calling envViewModel.add(config) or
envViewModel.update(old, config); allow the save if the name is unchanged from
editingOldName, otherwise check ConfigurationManager (or the envViewModel list)
and ConfigurationDefaults.isDefault(name) and surface an error/abort the save if
either returns true. Apply the same validation to the other save/edit handler
that uses the same add/update flow so edits cannot create ambiguous keys.
In
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/EnvViewModel.kt`:
- Around line 1-6: Update the file header in EnvViewModel.kt to use the original
creation-year-to-current-year range (e.g., replace the single year "2026" with
the original start year followed by "-2026"); edit the top block comment so the
copyright line reads "Copyright (c) <original-start-year>-2026 Ping Identity
Corporation. All rights reserved." to match the repo convention for existing
Kotlin/Java files and leave the rest of the header unchanged.
In
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/journey/JourneyViewModel.kt`:
- Around line 36-39: When ConfigurationManager.journey is null in
JourneyViewModel.start (the block with "val next =
ConfigurationManager.journey?.start(journeyName) ?: run { ... }"), set the view
state's error property as DaVinciViewModel.start does and then set loading to
false and return; specifically assign the correct JourneyState error field (use
the actual property name on JourneyState rather than a mismatched identifier)
with a message like "Journey not configured" so the UI shows an error instead of
silently doing nothing.
In
`@samples/pingsampleapp/src/test/kotlin/com/pingidentity/samples/pingsampleapp/config/ConfigurationManagerTest.kt`:
- Around line 190-221: The tests are not invoking the actual helpers under test;
update the three cases to call
ConfigurationManager.selectedConfig(ConfigType.JOURNEY) and
ConfigurationManager.hasConfiguration(ConfigType) (or whichever public helper
names exist) and assert their return values instead of using local Map/List
lookups; for the selectedConfig test ensure the ConfigurationManager is reset or
populated appropriately before asserting null, and for the hasConfiguration
tests populate ConfigurationManager with the sample configurations
(davinciConfig, journeyConfig) then assert
ConfigurationManager.hasConfiguration(ConfigType.JOURNEY) is true,
hasConfiguration(ConfigType.DAVINCI) is true, and
hasConfiguration(ConfigType.DEVICE) is false so the tests validate the actual
helpers.
- Around line 1-6: Replace the ranged copyright header at the top of
ConfigurationManagerTest.kt with the new-file header that uses only the current
year; change the "2024 - 2026" range to the single year "2026" in the file
header comment so the header for this new Kotlin file reflects the current year.
---
Outside diff comments:
In
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/userprofile/UserProfileViewModel.kt`:
- Around line 90-103: When ConfigurationManager.journey (and similarly
ConfigurationManager.daVinci and ConfigurationManager.oidc) is null the existing
branches do nothing and leave stale state; update the null-case to explicitly
reset that tab’s state by calling state.update to set the corresponding user
field (journeyUser/daVinciUser/oidcUser) to null and the corresponding error
field (journeyError/daVinciError/oidcError) to a cleared value before returning.
Locate the blocks using ConfigurationManager.journey?.journeyUser()?.let { user
-> ... } and the daVinciUserInfo()/oidcUserInfo() usages and add the fallback
state.update calls mirroring the success/failure resets so each tab is cleared
when its client/user is unavailable.
🪄 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: 47c62f39-d9dd-403b-9c97-beeb40106417
📒 Files selected for processing (24)
.gitignoreCHANGELOG.mdsamples/pingsampleapp/build.gradle.ktssamples/pingsampleapp/src/main/assets/ping_sdk_config.jsonsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/PingSampleApplication.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Configuration.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationDefaults.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationException.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationParser.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Env.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/EnvViewModel.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/DaVinciViewModel.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/davinci/collector/SocialLoginButton.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/devicemanagement/DeviceManagementViewModel.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/journey/JourneyViewModel.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/journey/callback/IdPCallback.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/logout/LogoutViewModel.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/oidc/CentralizeLoginViewModel.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/token/TokenViewModel.ktsamples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/userprofile/UserProfileViewModel.ktsamples/pingsampleapp/src/test/kotlin/com/pingidentity/samples/pingsampleapp/config/ConfigurationManagerTest.ktsamples/pingsampleapp/src/test/kotlin/com/pingidentity/samples/pingsampleapp/config/ConfigurationParserTest.ktsamples/pingsampleapp/src/test/kotlin/com/pingidentity/samples/pingsampleapp/config/ConfigurationTest.kt
| /* | ||
| * Copyright (c) 2024 - 2026 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| */ |
There was a problem hiding this comment.
Copyright header should use the current year only for new files.
This is a newly added file, so the header should read Copyright (c) 2026 Ping Identity Corporation rather than a 2024 - 2026 range.
📝 Proposed fix
-/*
- * Copyright (c) 2024 - 2026 Ping Identity Corporation. All rights reserved.
+/*
+ * Copyright (c) 2026 Ping Identity Corporation. All rights reserved.Based on learnings: for newly added files use only the current year (e.g., "Copyright (c) 2026 Ping Identity Corporation").
📝 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.
| /* | |
| * Copyright (c) 2024 - 2026 Ping Identity Corporation. All rights reserved. | |
| * | |
| * This software may be modified and distributed under the terms | |
| * of the MIT license. See the LICENSE file for details. | |
| */ | |
| /* | |
| * Copyright (c) 2026 Ping Identity Corporation. All rights reserved. | |
| * | |
| * This software may be modified and distributed under the terms | |
| * of the MIT license. See the LICENSE file for details. | |
| */ |
🤖 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
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Configuration.kt`
around lines 1 - 6, Update the copyright header in the new file Configuration.kt
to use only the current year (2026) instead of a range; replace "2024 - 2026"
with "2026" in the file header comment so it reads "Copyright (c) 2026 Ping
Identity Corporation".
| * | ||
| * @param config The configuration to check. | ||
| */ | ||
| fun isDefault(config: Configuration): Boolean = entries.any { it.name == config.name } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect how ConfigurationManager combines defaults + user configs and whether
# global name uniqueness is enforced, plus how isDefault gates edit/delete in UI.
fd -e kt 'ConfigurationManager|Env' | xargs rg -nC3 'isDefault|ConfigurationDefaults|user_configurations|\.name =='Repository: ForgeRock/ping-android-sdk
Length of output: 14118
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Show ConfigurationDefaults.kt around isDefault + how entries/defaults are built
echo "---- ConfigurationDefaults.kt (around isDefault) ----"
python3 - <<'PY'
import re, pathlib
p = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationDefaults.kt")
txt = p.read_text(encoding="utf-8")
for m in re.finditer(r'fun isDefault\s*\(', txt):
start = max(0, txt.rfind('\n', 0, m.start())-400)
end = min(len(txt), m.start()+400)
chunk = txt[start:end]
print(chunk)
break
PY
# 2) Show ConfigurationManager.kt: how defaults+user configs are merged; and any uniqueness checks
echo "---- ConfigurationManager.kt (merge/load + add/update/delete) ----"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.kt")
txt = p.read_text(encoding="utf-8")
patterns = [
r'init\s*\{',
r'readUserConfigs',
r'writeUserConfigs',
r'load',
r'val\s+defaults',
r'val\s+userConfigs',
r'suspend fun add',
r'suspend fun update',
r'suspend fun delete',
r'build.*Instance',
r'getAll|all\.firstOrNull'
]
for pat in patterns:
m = re.search(pat, txt)
if not m:
continue
s = max(0, txt.rfind('\n', 0, m.start())-200)
e = min(len(txt), m.start()+600)
print("\n### Match:", pat, "at", m.start())
print(txt[s:e])
PY
# 3) Locate where user configs are read/parsed and whether names are deduped/validated there
echo "---- Search for 'unique' / dedupe / duplicate / name checks in ConfigurationManager.kt ----"
rg -n --no-heading -S "unique|dedup|duplicate|dupe|enforce|require|check.*name|name.*check|distinct\\(|groupBy\\(\\s*\\{\\s*it\\.name" \
samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.kt
# 4) UI gating context (Env.kt) - confirm isDefault solely gates edit/delete
echo "---- Env.kt (around edit/delete gating) ----"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Env.kt")
txt = p.read_text(encoding="utf-8")
# print small window around where onEdit/onDelete are set
for m in re.finditer(r'onEdit\s*=\s*if\s*\(!isDefault\)', txt):
start = txt.rfind('\n', 0, m.start())-200
end = txt.find('\n', m.end())+200
print(txt[start:end])
break
PYRepository: ForgeRock/ping-android-sdk
Length of output: 9333
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- ConfigurationManager.kt: readUserConfigs() body ----"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.kt")
txt = p.read_text(encoding="utf-8")
m = re.search(r'fun\s+readUserConfigs\s*\(', txt)
if not m:
m = re.search(r'suspend\s+fun\s+readUserConfigs\s*\(', txt)
if not m:
print("readUserConfigs not found")
raise SystemExit(0)
start = txt.rfind('\n', 0, m.start())
# naive brace matching from first '{' after signature
brace_start = txt.find('{', m.end())
depth=0
i=brace_start
while i < len(txt):
if txt[i]=='{': depth+=1
elif txt[i]=='}':
depth-=1
if depth==0:
end=i+1
break
i+=1
else:
end=min(len(txt), m.end()+4000)
print(txt[start:end])
PY
echo "---- ConfigurationManager.kt: any dedupe/name checks in initialize/add/update/delete ----"
rg -n --no-heading -S "distinct\\(|groupBy\\(|associateBy\\(|Map<|\\bcontains\\(|\\bcheck\\b|require\\(|enforce|duplicate|dedup" \
samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.kt
echo "---- ConfigurationDefaults.kt: load()/entries definition ----"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationDefaults.kt")
txt = p.read_text(encoding="utf-8")
for pat in [r'fun\s+load\s*\(', r'private\s+.*entries', r'assets/ping_sdk_config\.json', r'ConfigurationParser\.parse']:
m = re.search(pat, txt)
if not m:
print("pattern not found:", pat)
continue
s = max(0, txt.rfind('\n', 0, m.start())-200)
e = min(len(txt), txt.find('\n', m.end())+800)
print("\n### match:", pat)
print(txt[s:e])
PY
echo "---- Find and inspect ConfigurationParser implementation ----"
fd -e kt 'ConfigurationParser' samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config | head -n 50
fd -e kt 'ConfigurationParser' samples/pingsampleapp/src/main/java | head -n 50Repository: ForgeRock/ping-android-sdk
Length of output: 4096
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- ConfigurationParser.kt (look for DuplicateName / uniqueness rules) ----"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationParser.kt")
txt = p.read_text(encoding="utf-8")
# print file with line numbers for key sections only
for term in ["DuplicateName", "unique", "duplicate", "name", "parse", "validate", "entries"]:
if term in txt:
pass
print("File length (lines):", txt.count("\\n")+1)
PY
rg -n --no-heading -S "DuplicateName|Duplicate|duplicate|DuplicateName|distinct|distinctBy|groupBy\\(|associateBy\\(|validate|throw" \
samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationParser.kt
echo "---- ConfigurationParser.kt around parse/validation ----"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationParser.kt")
txt = p.read_text(encoding="utf-8")
# Extract around parse function
m = re.search(r'fun\s+parse\s*\(', txt)
if not m:
raise SystemExit("parse() not found")
start = txt.rfind('\n', 0, m.start())
# find next 500 lines chunk by braces
brace_start = txt.find('{', m.end())
depth=0; i=brace_start
while i < len(txt):
if txt[i]=='{': depth+=1
elif txt[i]=='}':
depth-=1
if depth==0:
end=i+1
break
i+=1
else:
end=min(len(txt), m.end()+6000)
print(txt[max(0,start-200):min(len(txt),end+2000)])
PY
echo "---- ConfigurationManager.kt: deserializeUserConfigs() ----"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.kt")
txt = p.read_text(encoding="utf-8")
m = re.search(r'deserializeUserConfigs\s*\(', txt)
if not m:
m = re.search(r'fun\s+deserializeUserConfigs\s*\(', txt)
if not m:
raise SystemExit("deserializeUserConfigs not found")
# show around method
start = txt.rfind('\n', 0, m.start())
brace_start = txt.find('{', m.end())
depth=0; i=brace_start
while i < len(txt):
if txt[i]=='{': depth+=1
elif txt[i]=='}':
depth-=1
if depth==0:
end=i+1
break
i+=1
else:
end=min(len(txt), m.end()+4000)
print(txt[max(0,start-200):min(len(txt),end+800)])
PY
echo "---- ConfigurationManager.kt: any gating of edit/delete other than isDefault ----"
rg -n --no-heading -S "onEdit|onDelete|isDefault\\(" samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Env.kt samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.ktRepository: ForgeRock/ping-android-sdk
Length of output: 8347
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Where user config JSON is created/written
rg -n --no-heading -S "serializeUserConfigs|writeUserConfigs|USER_CONFIGS_KEY" \
samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.kt
# Look for any name-collision validation in UI/view-model when adding/updating configs
rg -n --no-heading -S "DuplicateName|duplicate name|name.*already|isDefault\\(|ConfigurationDefaults\\.|match.*defaults" \
samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config | head -n 200
# Find config add/update/edit entrypoints in Env/ViewModel
rg -n --no-heading -S "add\\(|update\\(|delete\\(|openAdd|openEdit|Config.*ViewModel|EnvViewModel" \
samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp | head -n 200Repository: ForgeRock/ping-android-sdk
Length of output: 13897
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect EnvViewModel and dialog / config editor code for name validation/collision checks
rg -n --no-heading -S "class EnvViewModel|openAdd\\(|openEdit\\(|add\\(config: Configuration\\)|update\\(oldName: String" \
samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config
rg -n --no-heading -S "Configuration.*Dialog|Edit.*Configuration|Add.*Configuration|Config.*Form|EditConfig|AddConfig|Journey|DaVinci|Oidc|DeviceClient|discoveryEndpoint|redirectUri|scopes" \
samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config
# Look for validation that references name equality or compares against defaults
rg -n --no-heading -S "ConfigurationDefaults\\.entries|isDefault\\(|ping_sdk_config\\.json|assets/ping_sdk_config\\.json|DuplicateName|already exists|name.*taken|existing.*name" \
samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config
# Print EnvViewModel.kt and Env.kt sections around add/edit handlers
python3 - <<'PY'
import pathlib, re
base = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config")
for fn in ["EnvViewModel.kt","Env.kt","EnvView.kt","ConfigRow.kt","ConfigurationEdit.kt","EditEnv.kt"]:
p = base/fn
if p.exists():
print("\n====", p, "====")
txt = p.read_text(encoding="utf-8")
# print file head or the relevant add/update/delete methods
m = re.search(r'fun\s+(openAdd|openEdit|add|update|delete)\b', txt)
if m:
s = max(0, txt.rfind("\n", 0, m.start())-200)
e = min(len(txt), m.start()+1200)
print(txt[s:e])
else:
print(txt[:500])
PYRepository: ForgeRock/ping-android-sdk
Length of output: 23569
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show how Env.kt builds the per-type config list that's iterated (and therefore whether isDefault is applied across types).
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Env.kt")
txt = p.read_text(encoding="utf-8")
# find the configs.forEach block (where isDefault is computed)
m = re.search(r'configs\\.forEach\\s*\\{\\s*config\\s*->', txt)
if not m:
# fallback: find "val isDefault" near that area
m = re.search(r'val isDefault\\s*=\\s*ConfigurationDefaults\\.isDefault\\(config\\)', txt)
print("fallback match at", m.start() if m else None)
else:
print("configs.forEach match at", m.start())
start = txt.rfind('\n', 0, (m.start() if m else 0)) - 250 if m else 0
start = max(0, start)
end = txt.find('\n', (m.start() if m else 0) ) + 450 if m else 450
end = min(len(txt), end+600)
print(txt[start:end])
PY
echo "---- Env.kt: ConfigFormState + toConfiguration() + save enablement around onSave/modal ----"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Env.kt")
txt = p.read_text(encoding="utf-8")
# print ConfigFormState definition + toConfiguration
m1 = re.search(r'data class ConfigFormState', txt)
m2 = re.search(r'fun\\s+toConfiguration\\(', txt)
# print block around save enablement (where it checks form fields)
m3 = re.search(r'form\\.[a-zA-Z0-9_]+\\.isNotBlank\\(\\)', txt)
# print around sheet confirm button validation
m4 = re.search(r'text = if \\(isEdit\\) "Edit Configuration" else "Add Configuration"', txt)
# function to print around an index
def around(idx, before=200, after=600):
s = max(0, txt.rfind('\n', 0, idx-before))
e = min(len(txt), idx+after)
return txt[s:e]
if m1:
print("\n==== ConfigFormState block ====")
print(around(m1.start(), before=0, after=260))
if m2:
print("\n==== ConfigFormState.toConfiguration() block ====")
print(around(m2.start(), before=0, after=320))
if m3:
print("\n==== Save enablement block (first isNotBlank occurrence) ====")
print(around(m3.start(), before=120, after=420))
if m4:
print("\n==== Modal save button block (around label) ====")
print(around(m4.start(), before=160, after=420))
PYRepository: ForgeRock/ping-android-sdk
Length of output: 2321
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Env.kt"
echo "---- Line numbers of key spots in Env.kt ----"
rg -n --no-heading "val isDefault\s*=\s*ConfigurationDefaults\.isDefault\(config\)" "$FILE" || true
rg -n --no-heading "onSave\(form" "$FILE" || true
rg -n --no-heading "data class ConfigFormState" "$FILE" || true
rg -n --no-heading "fun\s+toConfiguration\(" "$FILE" || true
rg -n --no-heading "isNotBlank\(\)" "$FILE" || true
rg -n --no-heading "Text\\(\"Add Configuration\"|Edit Configuration" "$FILE" || true
# print small ranges around the isDefault usage
LINE_ISDEFAULT=$(rg -n --no-heading "val isDefault\s*=\s*ConfigurationDefaults\.isDefault\(config\)" "$FILE" | head -n1 | cut -d: -f1)
if [ -n "${LINE_ISDEFAULT:-}" ]; then
echo "---- Env.kt around isDefault (edit/delete gating) ----"
sed -n "$((LINE_ISDEFAULT-25)),$((LINE_ISDEFAULT+25))p" "$FILE"
fi
# print save button enabled/disabled logic (first isNotBlank block)
LINE_NOTBLANK=$(rg -n --no-heading "isNotBlank\(\)" "$FILE" | head -n1 | cut -d: -f1 || true)
if [ -n "${LINE_NOTBLANK:-}" ]; then
echo "---- Env.kt around isNotBlank checks (save enablement) ----"
sed -n "$((LINE_NOTBLANK-80)),$((LINE_NOTBLANK+120))p" "$FILE"
fi
# print config form state + mapping
LINE_FORM=$(rg -n --no-heading "data class ConfigFormState" "$FILE" | head -n1 | cut -d: -f1 || true)
if [ -n "${LINE_FORM:-}" ]; then
echo "---- Env.kt around ConfigFormState ----"
sed -n "$((LINE_FORM-20)),$((LINE_FORM+80))p" "$FILE"
fi
LINE_TO=$(rg -n --no-heading "fun\s+toConfiguration\(" "$FILE" | head -n1 | cut -d: -f1 || true)
if [ -n "${LINE_TO:-}" ]; then
echo "---- Env.kt around toConfiguration() ----"
sed -n "$((LINE_TO-20)),$((LINE_TO+60))p" "$FILE"
fiRepository: ForgeRock/ping-android-sdk
Length of output: 13863
isDefault is name-only; a user config can reuse a bundled default name and then be treated as a default (edit/delete disabled and update/delete misbehave).
ConfigurationDefaults.isDefault returns true when any bundled default has the same Configuration.name, and Env.kt uses that to disable onEdit/onDelete for those rows. The add/edit sheet only checks form.name.isNotBlank() (no check against bundled default names), and ConfigurationParser enforces DuplicateName only within each parsed JSON list (defaults asset vs user JSON), not across them—so a persisted user config can collide with a default. Because ConfigurationManager.add/update/delete also rely on isDefault, the colliding user entry will be misclassified as “default” and won’t be editable/deletable (and update can skip replacing it).
Fix: make the default/user distinction use a stronger identity (e.g., (type, name)) or track a persisted “origin/source” flag and base isDefault on that; alternatively, reject user configs whose (type,) name collides with any bundled default.
🤖 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
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationDefaults.kt`
at line 65, ConfigurationDefaults.isDefault currently only checks
Configuration.name which lets a user config reuse a bundled default name and be
misclassified; update the logic so default/user identity is based on a stronger
key (e.g., check both Configuration.type and Configuration.name in
ConfigurationDefaults.isDefault or add a persisted origin/source flag on
Configuration and make isDefault consult that), and ensure ConfigurationParser
rejects user-provided configs whose (type, name) collide with any bundled
default; also update Env.kt UI checks (currently relying on isDefault) and
ConfigurationManager.add/update/delete to use the new identity so edit/delete
behavior is correct.
| /* | ||
| * Copyright (c) 2024 - 2026 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| */ |
There was a problem hiding this comment.
Use the new-file header format here.
This file is new, so the copyright header should use 2026 instead of a ranged 2024 - 2026. Based on learnings: new Kotlin/Java files in this repo should use only the current year in the copyright header.
🤖 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
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.kt`
around lines 1 - 6, The file header uses a ranged copyright year; update the
header in ConfigurationManager.kt to use the single current year "2026" instead
of "2024 - 2026" — locate the top-of-file comment block (the file-level header
above the ConfigurationManager class) and replace the ranged year with the
single year 2026 so the new-file header matches the repo convention.
| suspend fun add(config: Configuration) { | ||
| mutex.withLock { | ||
| val current = _configurations.value | ||
| val userConfigs = current.filter { !ConfigurationDefaults.isDefault(it) } + config | ||
| _configurations.value = current.filter { ConfigurationDefaults.isDefault(it) } + userConfigs | ||
| appContext?.let { writeUserConfigs(it, userConfigs) } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Replaces the user-added configuration whose [Configuration.name] equals [oldName] with | ||
| * [config]. If the replaced entry was the active selection for its type, the SDK instance | ||
| * is rebuilt with the new config. | ||
| */ | ||
| suspend fun update(oldName: String, config: Configuration) { | ||
| mutex.withLock { | ||
| val defaults = _configurations.value.filter { ConfigurationDefaults.isDefault(it) } | ||
| val userConfigs = _configurations.value | ||
| .filter { !ConfigurationDefaults.isDefault(it) } | ||
| .map { if (it.name == oldName) config else it } | ||
| _configurations.value = defaults + userConfigs | ||
|
|
||
| appContext?.let { writeUserConfigs(it, userConfigs) } | ||
|
|
||
| // If this config was selected, update the selection and rebuild. | ||
| val currentSelection = _selections.value[config.type] | ||
| if (currentSelection?.name == oldName) { | ||
| _selections.value = _selections.value + (config.type to config) | ||
| appContext?.let { writeSelection(it, config.type, config) } | ||
| rebuildInstance(config) | ||
| } |
There was a problem hiding this comment.
Validate configs before add() / update() persist them.
These mutation paths bypass ConfigurationParser, but the rest of the manager treats name as a stable identifier for selection, update, and delete. A duplicate or otherwise invalid config saved here can corrupt later mutations and, on the next launch, make deserializeUserConfigs() drop the entire user-config list. Please enforce the same validation/uniqueness rules here before committing state.
🤖 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
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/ConfigurationManager.kt`
around lines 185 - 215, The add(Configuration) and update(oldName,
Configuration) paths currently write userConfigs directly to _configurations and
writeUserConfigs without validating or enforcing uniqueness, risking duplicates
or invalid entries that break deserializeUserConfigs; before modifying state in
add and before mapping/replacing in update, run the same validation and
uniqueness checks used by ConfigurationParser (validate structure, required
fields, and that names are unique among userConfigs and do not collide with
ConfigurationDefaults), reject or normalize invalid configs, and only
persist/writeUserConfigs and update _configurations/_selections (and call
rebuildInstance) when validation passes.
| fun onSave(form: ConfigFormState) { | ||
| val config = form.toConfiguration() | ||
| val old = editingOldName | ||
| if (old == null) { | ||
| envViewModel.add(config) | ||
| } else { | ||
| envViewModel.update(old, config) | ||
| } |
There was a problem hiding this comment.
Share the same validation rules as ConfigurationParser.
canSave has drifted from the real configuration contract: it allows empty scopes, malformed redirectUri / discoveryEndpoint, and Journey configs without cookieName, while also blocking valid Journey configs that omit realm. onSave then persists that Configuration unchanged, so the UI can save values the parser would reject and the SDK builders will consume later.
Also applies to: 393-398
🤖 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
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Env.kt`
around lines 156 - 163, canSave and onSave diverge from the real configuration
contract and allow/persist values ConfigurationParser would reject; update
canSave to reuse the same validation logic as ConfigurationParser (e.g., call
the parser's validate/parse method or a shared isValidConfiguration function) to
enforce non-empty scopes, well-formed redirectUri/discoveryEndpoint, and
required Journey fields (cookieName unless parser allows omission, and allow
omission of realm if parser does), and modify onSave to run the same
parser/validator before envViewModel.add or envViewModel.update (reject/save
only if parser returns valid Configuration or convert form via the parser) so
the UI cannot persist configurations the SDK builders would reject; reference
ConfigFormState.toConfiguration(), ConfigurationParser, canSave, onSave, and
envViewModel.add/update when making the changes.
Reject duplicate configuration names before save.
Configuration.name is the persistence key, ConfigurationDefaults.isDefault() matches defaults by name, and ConfigurationManager.update() / delete() also target user configs by name. A collision with another user config or a bundled default makes edits/deletes ambiguous and can leave a user config effectively undeletable.
Also applies to: 291-300
🤖 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
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/Env.kt`
around lines 156 - 163, Reject saving when the new Configuration.name would
collide with any existing user configuration or a bundled default: in onSave
(use ConfigFormState.toConfiguration() and editingOldName) validate the
candidate name before calling envViewModel.add(config) or
envViewModel.update(old, config); allow the save if the name is unchanged from
editingOldName, otherwise check ConfigurationManager (or the envViewModel list)
and ConfigurationDefaults.isDefault(name) and surface an error/abort the save if
either returns true. Apply the same validation to the other save/edit handler
that uses the same add/update flow so edits cannot create ambiguous keys.
| /* | ||
| * Copyright (c) 2026 - 2026 Ping Identity Corporation. All rights reserved. | ||
| * Copyright (c) 2026 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| */ |
There was a problem hiding this comment.
Restore the original year range in this modified file header.
This is an existing file, so the header should keep its original creation year and extend it through 2026 instead of resetting to a single year. Based on learnings: existing Kotlin/Java files in this repo should use a creation-year-to-current-year range in the copyright header.
🤖 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
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/config/EnvViewModel.kt`
around lines 1 - 6, Update the file header in EnvViewModel.kt to use the
original creation-year-to-current-year range (e.g., replace the single year
"2026" with the original start year followed by "-2026"); edit the top block
comment so the copyright line reads "Copyright (c) <original-start-year>-2026
Ping Identity Corporation. All rights reserved." to match the repo convention
for existing Kotlin/Java files and leave the rest of the header unchanged.
| val next = ConfigurationManager.journey?.start(journeyName) ?: run { | ||
| loading.update { false } | ||
| return@launch | ||
| } |
There was a problem hiding this comment.
Surface an error state when Journey is not configured.
Unlike DaVinciViewModel.start() (which sets error = "DaVinci not configured" when the SDK is null), here a null ConfigurationManager.journey only flips loading to false and returns. The screen is left with no node and no error message, giving the user no feedback about why nothing happened. Consider mirroring the DaVinci behavior for consistency.
🩹 Suggested change
viewModelScope.launch {
val next = ConfigurationManager.journey?.start(journeyName) ?: run {
+ state.update { it.copy(error = "Journey not configured") }
loading.update { false }
return@launch
}Adjust the field name to match JourneyState's actual error property.
🤖 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
`@samples/pingsampleapp/src/main/java/com/pingidentity/samples/pingsampleapp/journey/JourneyViewModel.kt`
around lines 36 - 39, When ConfigurationManager.journey is null in
JourneyViewModel.start (the block with "val next =
ConfigurationManager.journey?.start(journeyName) ?: run { ... }"), set the view
state's error property as DaVinciViewModel.start does and then set loading to
false and return; specifically assign the correct JourneyState error field (use
the actual property name on JourneyState rather than a mismatched identifier)
with a message like "Journey not configured" so the UI shows an error instead of
silently doing nothing.
| /* | ||
| * Copyright (c) 2024 - 2026 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| */ |
There was a problem hiding this comment.
Use the new-file header format here.
This file is new, so the copyright header should use 2026 instead of a ranged 2024 - 2026. Based on learnings: new Kotlin/Java files in this repo should use only the current year in the copyright header.
🤖 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
`@samples/pingsampleapp/src/test/kotlin/com/pingidentity/samples/pingsampleapp/config/ConfigurationManagerTest.kt`
around lines 1 - 6, Replace the ranged copyright header at the top of
ConfigurationManagerTest.kt with the new-file header that uses only the current
year; change the "2024 - 2026" range to the single year "2026" in the file
header comment so the header for this new Kotlin file reflects the current year.
| @Test | ||
| fun `selectedConfig returns null when no configuration has been selected for that type`() { | ||
| // Access the in-memory StateFlow values directly; no Context needed. | ||
| // After construction (before initialize), selections is empty. | ||
| val selection = ConfigurationManager.selectedConfig(ConfigType.JOURNEY) | ||
| // The object may have been previously initialized in other tests (it's a singleton), | ||
| // so we test the helper's behaviour with an explicit map lookup. | ||
| val emptyMap = emptyMap<ConfigType, Configuration>() | ||
| val result = emptyMap[ConfigType.JOURNEY] | ||
| assertEquals(null, result) | ||
| } | ||
|
|
||
| @Test | ||
| fun `hasConfiguration returns false when configurations list is empty`() { | ||
| val emptyList = emptyList<Configuration>() | ||
| val result = emptyList.any { it.type == ConfigType.JOURNEY } | ||
| assertEquals(false, result) | ||
| } | ||
|
|
||
| @Test | ||
| fun `hasConfiguration returns true when matching type exists`() { | ||
| val configs = listOf( | ||
| davinciConfig(name = "DaVinci A"), | ||
| journeyConfig(name = "Journey A"), | ||
| ) | ||
| val hasJourney = configs.any { it.type == ConfigType.JOURNEY } | ||
| val hasDaVinci = configs.any { it.type == ConfigType.DAVINCI } | ||
| val hasDevice = configs.any { it.type == ConfigType.DEVICE } | ||
|
|
||
| assertTrue(hasJourney) | ||
| assertTrue(hasDaVinci) | ||
| assertEquals(false, hasDevice) |
There was a problem hiding this comment.
These tests are not asserting the ConfigurationManager helpers.
selectedConfig() is called and then ignored, and the hasConfiguration() cases only assert against local Map/List.any expressions. These tests will still pass if either helper regresses, so they are not covering the behavior their names claim to verify.
🤖 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
`@samples/pingsampleapp/src/test/kotlin/com/pingidentity/samples/pingsampleapp/config/ConfigurationManagerTest.kt`
around lines 190 - 221, The tests are not invoking the actual helpers under
test; update the three cases to call
ConfigurationManager.selectedConfig(ConfigType.JOURNEY) and
ConfigurationManager.hasConfiguration(ConfigType) (or whichever public helper
names exist) and assert their return values instead of using local Map/List
lookups; for the selectedConfig test ensure the ConfigurationManager is reset or
populated appropriately before asserting null, and for the hasConfiguration
tests populate ConfigurationManager with the sample configurations
(davinciConfig, journeyConfig) then assert
ConfigurationManager.hasConfiguration(ConfigType.JOURNEY) is true,
hasConfiguration(ConfigType.DAVINCI) is true, and
hasConfiguration(ConfigType.DEVICE) is false so the tests validate the actual
helpers.
JIRA Ticket
SDKS-5065
Description
Standardizing the configuration of Journey, DaVinci and OidcWeb
Summary by CodeRabbit
Release Notes
New Features
Documentation
Tests
Chores