Skip to content

Conversation

@SaintPatrck
Copy link
Contributor

@SaintPatrck SaintPatrck commented Nov 11, 2025

🎟️ Tracking

PM-28086

📔 Objective

This PR introduces a new :testharness module - a standalone Android application for validating Bitwarden's Credential Manager provider implementation.

Purpose:
The test harness allows developers to exercise and verify all Credential Manager API operations that the Bitwarden app supports as a credential provider:

  • Password Operations: Create and retrieve password credentials via CreatePasswordRequest and GetPasswordOption
  • Passkey Operations: Create and retrieve passkeys via CreatePublicKeyCredentialRequest and GetPublicKeyCredentialOption
  • Combined Operations: Test hybrid flows that accept both passwords and passkeys

Architecture:
The module follows the same patterns as :app and :authenticator:

  • MVVM + UDF architecture with BaseViewModel
  • Hilt dependency injection throughout
  • Jetpack Compose UI with Material 3
  • Comprehensive unit test coverage (ViewModels, Screens, and data layer)

Integration:
The test harness acts as a credential consumer client, triggering the system credential picker and allowing selection of Bitwarden as the provider. This enables end-to-end validation of credential flows without relying on external websites or apps.

Build Configuration:
Added :testharness to detekt (static analysis) and kover (code coverage) configurations in root build.gradle.kts.

Requirements: API 28+ (uses AndroidX Credentials library with Play Services compatibility)

📸 Screenshots

Coming soon!

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@github-actions
Copy link
Contributor

github-actions bot commented Nov 11, 2025

Logo
Checkmarx One – Scan Summary & Detailseee8fc17-4644-42f4-a65e-587216aa81a8

New Issues (21)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 412
detailsMethod Lambda at line 412 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewMode...
ID: VNmzVa0uU4O4m4DsuDv0bbJlT9Q%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 269
detailsMethod Lambda at line 269 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewMode...
ID: L%2BgShHNRz5iUOo2EC2C8MgIpG00%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 311
detailsMethod Lambda at line 311 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewMode...
ID: e8T5z3fvMRdFMpadz77AL7No%2FvU%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 343
detailsMethod Lambda at line 343 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewMode...
ID: pPXcv28ABu%2F6GA1WOpFD0TboSgE%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 396
detailsMethod Lambda at line 396 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewMode...
ID: pgvUN7lZCbYzaAOJH%2B9fUNTVB%2B8%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 441
detailsMethod Lambda at line 441 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewMode...
ID: rDF8%2BsENcJdd8yDZ4TFRxPlTgQY%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 67
detailsMethod Lambda at line 67 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModel...
ID: 934hGiWsl8t616gVJhKkiyRBgsY%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 240
detailsMethod Lambda at line 240 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewMode...
ID: GUBNnvgY0lv%2BGqcSO%2FnNuciKbXU%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 206
detailsMethod Lambda at line 206 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewMode...
ID: YouKVI4Jdtl%2FNstQSqHOWmhxvHI%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 168
detailsMethod Lambda at line 168 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewMode...
ID: qbHZ9jkhEGlQZL2pLee%2FK6yy9mo%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 107
detailsMethod Lambda at line 107 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewMode...
ID: K0wj5hf34ZiZylfvhV4cz3Y7ECs%3D
Attack Vector
MEDIUM Privacy_Violation /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 87
detailsMethod Lambda at line 87 of /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModel...
ID: 6jVYKBZ1BMVRKaBw7sToRrVC8wk%3D
Attack Vector
MEDIUM Use_of_Hardcoded_Password /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 296
detailsThe application uses the hard-coded password "SecurePassword123!" for authentication purposes, either using it to verify users' identities, or to...
ID: aWLVwSZgBsoEx%2BeFNB6WKZQHsdM%3D
Attack Vector
MEDIUM Use_of_Hardcoded_Password /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 190
detailsThe application uses the hard-coded password "SecurePassword123!" for authentication purposes, either using it to verify users' identities, or to...
ID: nRjXN0VBoqsg80XuD6YxBLhaMcE%3D
Attack Vector
MEDIUM Use_of_Hardcoded_Password /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 224
detailsThe application uses the hard-coded password "SecurePassword123!" for authentication purposes, either using it to verify users' identities, or to...
ID: wkRK39YIL%2Beu0QfNq3lebfWX%2FzM%3D
Attack Vector
MEDIUM Use_of_Hardcoded_Password /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 258
detailsThe application uses the hard-coded password "SecurePassword123!" for authentication purposes, either using it to verify users' identities, or to...
ID: OP0nloj9CIHpo36z5MqD%2BrRzE64%3D
Attack Vector
MEDIUM Use_of_Hardcoded_Password /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 124
detailsThe application uses the hard-coded password "SecurePassword123!" for authentication purposes, either using it to verify users' identities, or to...
ID: lyH2yC56qKRMvuwTwrKVmVyo%2FF0%3D
Attack Vector
MEDIUM Use_of_Hardcoded_Password /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 157
detailsThe application uses the hard-coded password "SecurePassword123!" for authentication purposes, either using it to verify users' identities, or to...
ID: LwjJ3PLCQ46%2FtPDRy7i1zYXjz2k%3D
Attack Vector
MEDIUM Use_of_Hardcoded_Password /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 428
detailsThe application uses the hard-coded password "SecurePassword123!" for authentication purposes, either using it to verify users' identities, or to...
ID: BOT4N96f0lRsiaI5WPMXwS%2F%2BzMg%3D
Attack Vector
MEDIUM Use_of_Hardcoded_Password /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 328
detailsThe application uses the hard-coded password "SecurePassword123!" for authentication purposes, either using it to verify users' identities, or to...
ID: 4ZBFsS%2FnteoqZu1lZPN%2FI5HJmFc%3D
Attack Vector
MEDIUM Use_of_Hardcoded_Password /testharness/src/test/kotlin/com/x8bit/bitwarden/testharness/ui/platform/feature/createpassword/CreatePasswordViewModelTest.kt: 51
detailsThe application uses the hard-coded password "SecurePassword123!" for authentication purposes, either using it to verify users' identities, or to...
ID: qulrrCLKz4bXmrFoT%2BfndIEu6V8%3D
Attack Vector

Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

Checkmarx One found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@SaintPatrck SaintPatrck changed the title Add testharness module for Credential Manager and Autofill e2e testing [PM-28086] Add testharness module for Credential Manager and Autofill e2e testing Nov 11, 2025
@SaintPatrck SaintPatrck changed the title [PM-28086] Add testharness module for Credential Manager and Autofill e2e testing [PM-28086] Add testharness module for Credential Manager and Autofill testing Nov 11, 2025
@SaintPatrck SaintPatrck changed the title [PM-28086] Add testharness module for Credential Manager and Autofill testing [PM-28086] Add testharness for Credential Manager and Autofill testing Nov 11, 2025
@codecov
Copy link

codecov bot commented Nov 12, 2025

Codecov Report

❌ Patch coverage is 94.25926% with 62 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.67%. Comparing base (7120eef) to head (a5db63e).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...tform/feature/createpasskey/CreatePasskeyScreen.kt 87.65% 2 Missing and 8 partials ⚠️
...rness/ui/platform/feature/rootnav/RootNavScreen.kt 65.00% 6 Missing and 1 partial ⚠️
...rness/ui/platform/feature/landing/LandingScreen.kt 84.21% 2 Missing and 4 partials ⚠️
...e/credentialmanager/CredentialManagerListScreen.kt 94.28% 2 Missing and 2 partials ⚠️
...n/com/x8bit/bitwarden/testharness/MainViewModel.kt 62.50% 3 Missing ⚠️
.../testharness/data/manager/CredentialTestManager.kt 0.00% 3 Missing ⚠️
...form/feature/autofill/AutofillPlaceholderScreen.kt 81.25% 2 Missing and 1 partial ⚠️
...orm/feature/createpassword/CreatePasswordScreen.kt 95.31% 2 Missing and 1 partial ⚠️
...ui/platform/feature/getpasskey/GetPasskeyScreen.kt 95.45% 2 Missing and 1 partial ⚠️
.../platform/feature/getpassword/GetPasswordScreen.kt 94.44% 2 Missing and 1 partial ⚠️
... and 9 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6159      +/-   ##
==========================================
- Coverage   84.99%   84.67%   -0.33%     
==========================================
  Files         723      870     +147     
  Lines       52742    56145    +3403     
  Branches     7652     7799     +147     
==========================================
+ Hits        44827    47538    +2711     
- Misses       5242     5895     +653     
- Partials     2673     2712      +39     

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

@SaintPatrck SaintPatrck marked this pull request as ready for review November 12, 2025 14:49
@claude
Copy link
Contributor

claude bot commented Nov 12, 2025

Claude finished @SaintPatrck's task —— View job


Code Review Complete

Overall Assessment: APPROVE

This PR introduces a well-architected test harness module that follows established Bitwarden Android patterns. The author has addressed previous review feedback appropriately by extracting WebAuthn JSON generation, adding coverage exclusions for navigation/utility code, and expanding test coverage. The implementation is solid with good separation of concerns.


Summary

Strengths:

  • Follows MVVM + UDF architecture consistently
  • Proper Hilt DI throughout
  • Comprehensive unit tests for ViewModels and Screens (20 test files for 35 source files)
  • Successfully integrated into build system (detekt, kover)
  • Clear separation: test harness is credential consumer, not provider
  • Author addressed previous review findings effectively

Security Notes:

  • Checkmarx findings are false positives (hardcoded passwords in test files, privacy violations in test logging)
  • WebAuthn challenge generation properly documented as test-only with security warnings
  • No actual security concerns for a test harness module

Coverage:

  • 81.50% patch coverage is acceptable for a test harness
  • Navigation files properly annotated with @OmitFromCoverage
  • WebAuthnJsonBuilder properly annotated with @OmitFromCoverage
  • Missing coverage is primarily in CredentialTestManagerImpl integration paths (acceptable - tested via manual flows)

Detailed Findings

Finding 1: Checkmarx security findings are false positives

The 21 Checkmarx findings (Privacy_Violation and Use_of_Hardcoded_Password) all occur in test files where:

  • Hardcoded passwords like "SecurePassword123!" are test fixtures
  • Logged data is for debugging test flows
  • This is standard practice in test code

Recommendation: Mark these findings as false positives in Checkmarx. They do not represent actual security vulnerabilities.

Context

Test files naturally contain:

  • Hardcoded test credentials (line 51 in CreatePasswordViewModelTest.kt: val testPassword = "SecurePassword123!")
  • Privacy-flagged logging for debugging (ViewModel result text including usernames/passwords for test validation)

These are intentional and necessary for testing credential flows.

Finding 2: Navigation files now properly excluded from coverage

Resolved - Author added @OmitFromCoverage annotations to all navigation files per previous feedback. Navigation extension functions are correctly excluded as they're difficult to unit test and primarily delegate to framework code.

Verified in CreatePasswordNavigation.kt:1-2:

@file:OmitFromCoverage

package com.x8bit.bitwarden.testharness.ui.platform.feature.createpassword

Finding 3: WebAuthn JSON builder successfully extracted

Resolved - Author extracted WebAuthn JSON generation into WebAuthnJsonBuilder.kt per previous feedback (commit 646c2ac). This improves:

  • Separation of concerns (CredentialTestManagerImpl focuses on API calls)
  • Testability (JSON generation is independent)
  • Maintainability (WebAuthn spec changes isolated)
  • Security documentation (warnings about test-only usage at file level)

testharness/src/main/kotlin/com/x8bit/bitwarden/testharness/data/util/WebAuthnJsonBuilder.kt:3,15,23-25

Security documentation
@OmitFromCoverage
object WebAuthnJsonBuilder {

    /**
     * Build a minimal valid WebAuthn registration request JSON.
     *
     * This follows the WebAuthn specification for PublicKeyCredentialCreationOptions.
     *
     * **WARNING: TEST HARNESS ONLY** - This implementation uses simplified challenge
     * generation suitable for testing. Production implementations should use a secure
     * backend service to generate and validate challenges.

Finding 4: Test coverage significantly improved

Resolved - Author expanded test suite (commit d70615b). The module now has:

  • 20 test files covering 35 source files (57% file coverage)
  • Comprehensive ViewModel tests with success/error/cancellation paths
  • Screen composition tests
  • Well-structured nested test classes

Example from CredentialTestManagerTest.kt:32-72 showing proper test organization:

@Nested
inner class CreatePassword {
    @Test
    fun `success without origin returns Success`() = runTest { ... }
    
    @Test
    fun `success with origin returns Success`() = runTest { ... }

Remaining gap: CredentialTestManagerImpl integration coverage (18.79%) is low, but this is acceptable because:

  1. It's Android framework integration code (hard to unit test)
  2. Manual E2E testing validates these flows
  3. Error paths rely on CredentialManager exceptions

💭 Finding 5: Empty sealed classes in MainViewModel

MainAction and MainEvent are empty sealed classes. Author's previous response: "The empty classes will remain to simplify future expansion."

Accepted - This follows the BaseViewModel pattern consistently and allows future growth without breaking changes. The comments explain the intent clearly (MainViewModel.kt:43-55).

Alternative consideration

If truly no actions/events will ever be needed, could use Nothing type:

class MainViewModel : BaseViewModel<MainState, Nothing, Nothing>

However, keeping sealed classes for consistency is reasonable, especially with clear TODO comments about potential future use.


Architecture Compliance

MVVM + UDF: All ViewModels extend BaseViewModel with proper State/Action/Event separation
Hilt DI: @HiltViewModel on all VMs, @Inject constructors, proper module setup
Compose UI: Material 3, reusable Bitwarden components, proper state hoisting
Error Handling: CredentialTestResult sealed class (no exceptions in business logic)
Testing: JUnit 5, MockK, Turbine for Flow testing
Module Organization: Follows established patterns (:app, :authenticator, :testharness)
Build Integration: Added to detekt (static analysis) and kover (coverage) in root build.gradle.kts


Module Purpose Validation

The README clearly explains this is a credential consumer testing tool, not a provider. Key validations:

  1. Dependencies: Only consumer-side APIs (androidx.credentials)
  2. Asset Links: References Bitwarden's assetlinks with clear comment explaining test harness role (asset_statements.xml:3-6)
  3. Architecture: Standalone app module with no provider implementation
  4. Use Case: Manual E2E testing of Bitwarden's credential provider via system picker

This is the correct approach for validating the main app's credential provider implementation.


Minor Observations (No Action Required)

🎨 MainViewModel could use Nothing type if no actions/events will be added, but keeping sealed classes for consistency is acceptable

📝 ProGuard rules are minimal and appropriate for a debug/test tool (proguard-rules.pro)

📋 Build configuration properly sets applicationIdSuffix = ".dev" for debug builds to avoid conflicts


Recommendation: Approve and merge. This is a well-implemented test tool that will be valuable for validating Credential Manager integration.

Introduces a new standalone :testharness Android application module for
end-to-end validation of Bitwarden's Credential Manager provider and
Autofill framework implementations.

Purpose:
The test harness acts as a credential consumer client, allowing developers
to trigger and verify all credential operations that Bitwarden supports as
a provider without relying on external websites or applications.

Supported Test Flows:
- Password Creation: CreatePasswordRequest via Credential Manager
- Password Retrieval: GetPasswordOption via Credential Manager
- Passkey Creation: CreatePublicKeyCredentialRequest (FIDO2)
- Passkey Authentication: GetPublicKeyCredentialOption (FIDO2)
- Hybrid Operations: Combined password/passkey retrieval flows
- Origin Parameter Testing: Privileged app simulation with custom origins
- Autofill Framework: Placeholder for future autofill service testing

Architecture:
- MVVM + UDF: BaseViewModel with State/Action/Event patterns
- Hilt DI: Full dependency injection throughout all layers
- Jetpack Compose: Material 3 UI with BitwardenTheme integration
- Navigation: Multi-screen architecture matching :app and :authenticator
- Error Handling: Sealed Result classes, no exception-based handling

Module Structure:
- Activity layer: MainActivity with theme management
- Navigation: RootNavScreen orchestrating all test flows
- Data layer: CredentialTestManager wrapping AndroidX Credentials APIs
- UI layer: Feature screens for each credential operation type
- Test coverage: Comprehensive unit tests for all ViewModels and Screens

Build Configuration:
- Added :testharness to settings.gradle.kts
- Integrated with detekt static analysis
- Integrated with kover code coverage reporting
- API 28+ requirement (AndroidX Credentials with Play Services)

Requirements:
- Android API 28+
- Bitwarden app installed as credential provider
- Google Play Services (for API 28-33 compatibility)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
SaintPatrck and others added 6 commits November 12, 2025 10:38
This commit addresses UI layout issues on testharness screens, particularly on devices with smaller displays where content could be pushed off-screen or overflow when the software keyboard is visible.

The primary change is removing fixed heights and weights from the result `OutlinedTextField` components across multiple screens. These modifiers were making the text fields inflexible, preventing the layout from shrinking to accommodate the keyboard. By removing them, the text fields now dynamically resize based on their content, allowing the entire screen to scroll when necessary.

Specific changes:
- Removed fixed height and weight modifiers from result text fields in `CreatePasswordScreen`, `GetPasskeyScreen`, `GetPasswordScreen`, `CreatePasskeyScreen`, and `GetPasswordOrPasskeyScreen` to allow them to resize with their content.
- Added vertical scrolling to `GetPasswordOrPasskeyScreen` to ensure all fields remain accessible when the keyboard is open.
- Added bottom navigation bar padding to the `LandingScreen` to prevent the last button from being obscured by system navigation gestures.
This commit refactors the testing for `CredentialTestManagerImpl` by replacing the existing limited test file with a comprehensive new suite that covers all public methods and their various outcomes.

The new test suite, `CredentialTestManagerTest`, provides detailed coverage for creating and retrieving both passwords and passkeys. It uses nested test classes for better organization and clarity. Key scenarios tested include successful operations (with and without an origin), user cancellations, API exceptions, and unexpected response types, ensuring the manager's logic is robust across different credential management flows.

Specific changes:
- Deleted the old, sparsely populated `CredentialTestManagerImplTest.kt`.
- Created `CredentialTestManagerTest.kt` with comprehensive unit tests for:
  - `createPassword`
  - `getPassword`
  - `createPasskey`
  - `getPasskey`
  - `getPasswordOrPasskey`
- Added tests for success, cancellation, and error states for each method.
- Implemented mocking for various `androidx.credentials` classes and exceptions to simulate different API responses.
Exclude navigation boilerplate from coverage analysis as these files
contain primarily routing logic that provides minimal value when tested
in isolation.

Files annotated:
- AutofillPlaceholderNavigation.kt
- CreatePasswordNavigation.kt
- CreatePasskeyNavigation.kt
- CredentialManagerListNavigation.kt
- GetPasskeyNavigation.kt
- GetPasswordNavigation.kt
- GetPasswordOrPasskeyNavigation.kt
- LandingNavigation.kt

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Create WebAuthnJsonBuilder utility class to separate WebAuthn JSON
generation from credential management logic, as suggested in PR review.

Changes:
- Add WebAuthnJsonBuilder.kt with buildPasskeyCreationJson and
  buildPasskeyAuthenticationJson methods
- Update CredentialTestManagerImpl to use WebAuthnJsonBuilder
- Remove private JSON building methods from CredentialTestManagerImpl
- Remove CHALLENGE_SEED_SIZE constant (moved to WebAuthnJsonBuilder)

This improves separation of concerns and makes JSON building logic
independently testable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
This commit addresses compiler warnings for unused exception variables in cancellation catch blocks. The exception variable `e` was declared but never used in `CreateCredentialCancellationException` and `GetCredentialCancellationException` handlers.

The variable `e` has been replaced with `_` to signify that it is intentionally unused, resolving the warnings and improving code clarity.
Add the `@OmitFromCoverage` annotation to the `WebAuthnJsonBuilder` test harness utility. This object is used for generating test data and is not part of the production application code, so it should be excluded from code coverage metrics.

Additionally, add warning comments to the `buildCreationRequest` and `buildAuthenticationRequest` methods, clarifying that the simplified challenge generation is for testing purposes only and not suitable for production use.
when (result) {
is CreatePasswordResponse -> {
CredentialTestResult.Success(
message = "Password created successfully",
Copy link
Collaborator

Choose a reason for hiding this comment

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

The use of a hard-coded message seems a bit off. Should this not come from somewhere else or be defined in the UI layer?

password: String,
origin: String?,
): CredentialTestResult {
return try {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is everything wrapped in a giant try-catch?

navigation<AutofillGraphRoute>(
startDestination = AutofillPlaceholderRoute,
) {
composable<AutofillPlaceholderRoute> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Normally we would have a AutofillPlaceholderNavigation file to put the route and destination.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think I see what happened. Maybe we can still break inner destination out to a separate function.

Additionally, it should use the composableWithRootPushTransitions helper, right?

scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
navigationIcon = NavigationIcon(
navigationIcon = rememberVectorPainter(id = BitwardenDrawable.ic_back),
navigationIconContentDescription = "Back",
Copy link
Collaborator

Choose a reason for hiding this comment

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

I know we don't really need translations here but can we still move this stuff to a strings.xml file

onExecuteClick = { viewModel.trySendAction(CreatePasskeyAction.ExecuteClick) },
isLoading = state.isLoading,
onClearResultClick = { viewModel.trySendAction(CreatePasskeyAction.ClearResultClick) },
resultText = state.resultText,
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should really just be passing in the entire state class.

value = rpId,
onValueChange = onRpIdChange,
placeholder = stringResource(R.string.rp_id_hint),
cardStyle = null,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should these have cards?

I assume we want it to match our style


Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we ditch the Column and just use a regular Spacer

topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.create_passkey_title),
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

The scaffold and TopAppBar should be using the same scrollBehavior


private fun timestamp(): String {
val format = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
return "[${format.format(Date())}]"
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should not be using Date and we should not be using hardcoded date formats

topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.create_password_title),
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

The scroll behavior should be linked to the scrolling content

.padding(horizontal = 16.dp)
.imePadding()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Re generally avoid spacedBy arrangements since it removes flexibility if we ever need a smaller spacing.


Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp),
Copy link
Collaborator

Choose a reason for hiding this comment

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

This column could be replace by a single Spacer.

) {
BitwardenFilledButton(
label = stringResource(R.string.execute),
onClick = remember(viewModel) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is using a remembered viewModel but previous actions were not. We should make this consistent

modifier = Modifier.fillMaxWidth(),
)

Spacer(modifier = Modifier.height(16.dp))
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is missing navigation bar padding


private fun timestamp(): String {
val format = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
return "[${format.format(Date())}]"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same thing here with the hardcoded date format and use of Date class.

navigation<CredentialManagerGraphRoute>(
startDestination = CredentialManagerListRoute,
) {
composable<CredentialManagerListRoute> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this be broken out into it's own method.

Also, shouldn't it use the composableWithRootPushTransitions helper function

cardStyle = CardStyle.Bottom,
modifier = Modifier.standardHorizontalMargin(),
)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is missing bottom padding and navigation bar padding.

There is some extra padding on the column itself but that breaks edge-to-edge.

}

android {
namespace = "com.x8bit.bitwarden.testharness"
Copy link
Collaborator

Choose a reason for hiding this comment

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

More of a curiosity than anything else, but why did we go with the x8bit here?


compileOptions {
sourceCompatibility = JavaVersion.valueOf(
libs.versions.jvmTarget.get().replace(".", "_").let { "VERSION_$it" },
Copy link
Collaborator

@david-livefront david-livefront Nov 13, 2025

Choose a reason for hiding this comment

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

Why does this need to modify the value?


<activity
android:name=".MainActivity"
android:exported="true"
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should be observing the uiMode configChanges

modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
.imePadding(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

This screen should scroll

Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should be using standard margin on the items

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.

3 participants