Skip to content

feat(tests): add authenticated test attempt flow - #51

Merged
CowboyGH merged 13 commits into
developfrom
feature/auth-tests-attempt
Mar 29, 2026
Merged

CowboyGH merged 13 commits into
developfrom
feature/auth-tests-attempt

Conversation

@CowboyGH

@CowboyGH CowboyGH commented Mar 29, 2026 •

Copy link
Copy Markdown
Owner

🚀 Summary

Implemented the authenticated test attempt flow for the /tests root tab by reusing the existing tests/attempt business logic, adding auth API/repository support, normalizing shared attempt DTOs, and wiring a fullscreen attempt screen that mirrors the Fitness Start attempt UI.

✨ Changes

  • ✅ Added authenticated test attempt API endpoints to TestsApiClient
  • ✅ Added auth start DTOs for /api/tests/{testing}/start
  • ✅ Added AuthenticatedTestAttemptRepositoryImpl for auth test attempts + unit tests
  • ✅ Added a shared test-attempt result payload validator for the attempt repositories
  • ✅ Updated FitnessStartTestAttemptPageBuilder to resolve the dedicated guest attempt repository explicitly
  • ✅ Added fullscreen authenticated test attempt route, route paths, and page builder
  • ✅ Replaced tests catalog card navigation from the debug screen to the authenticated attempt flow
  • ✅ Reused existing TestAttemptCubit / TestAttemptState without changing their public contract

⚠️ Breaking Changes

  • Renamed shared test-attempt DTOs from guest-prefixed names to neutral request/response models because the same payload shapes are now reused by both guest and authenticated flows
  • Split the attempt repository contract into dedicated GuestTestAttemptRepository and AuthenticatedTestAttemptRepository bindings instead of relying on a single shared TestAttemptRepository DI registration

🧪 Verification

  • flutter analyze
  • flutter test test/features/tests/attempt
  • Manual flow: authenticated user opens /tests, starts a test, progresses through exercises, submits pulse, and returns to the tests catalog

@CowboyGH CowboyGH self-assigned this Mar 29, 2026
@CowboyGH CowboyGH added area: network API, requests, and data parsing area: ui/ux Widgets, layout, animations, or design area: logic State management and business logic type: feature New feature or request breaking-change Changes that break current API or compatibility labels Mar 29, 2026
@coderabbitai

coderabbitai Bot commented Mar 29, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@CowboyGH has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 30 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 8 minutes and 30 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2b37ec3b-2da2-4579-82d0-54c0860473b9

📥 Commits

Reviewing files that changed from the base of the PR and between b1b79e0 and e96a794.

📒 Files selected for processing (1)
  • test/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl_test.dart
📝 Walkthrough

Walkthrough

This PR adds an authenticated test-attempt flow alongside the existing guest flow. It introduces new DTOs and API paths for authenticated start/save/complete operations, splits the repository contract into GuestTestAttemptRepository and AuthenticatedTestAttemptRepository with separate implementations, updates DI to register both implementations, adds router paths and a TestsAttemptPage/Builder for the authenticated UI, and changes tests catalog navigation to open the authenticated attempt route. The shared TestAttemptCubit and its domain contract remain unchanged.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Router as GoRouter
    participant UI as TestsAttemptPage
    participant Cubit as TestAttemptCubit
    participant Repo as AuthenticatedTestAttemptRepository
    participant API as TestsApiClient
    participant BE as Backend

    User->>Router: Navigate to /tests/attempt/:testingId
    Router->>UI: Build TestsAttemptPageBuilder (testingId)
    UI->>Cubit: create(di<AuthenticatedTestAttemptRepository>) / startTest(testingId)
    Cubit->>Repo: startTest(testingId)
    Repo->>API: startTest(testingId)
    API->>BE: GET /api/tests/:testingId/start
    BE-->>API: StartTestResponseDto
    API-->>Repo: StartTestDataDto
    Repo-->>Cubit: Result<TestAttemptStart>
    Cubit-->>UI: update state (show exercise)

    User->>UI: choose result (1-4)
    UI->>Cubit: saveResult(attemptId, exerciseId, value)
    Cubit->>Repo: saveResult(...)
    Repo->>API: saveTestResult(attemptId, SaveTestResultRequestDto)
    API->>BE: POST /api/test-attempts/:attemptId/result
    BE-->>API: SaveTestResultResponseDto
    API-->>Repo: SaveTestResultDataDto
    Repo-->>Cubit: Result<TestAttemptResult>
    Cubit-->>UI: update state (next exercise or pulse)

    User->>UI: submit pulse
    UI->>Cubit: completeTest(attemptId, pulse)
    Cubit->>Repo: completeTest(attemptId, pulse)
    Repo->>API: completeTest(attemptId, CompleteTestRequestDto)
    API->>BE: POST /api/test-attempts/:attemptId/complete
    BE-->>API: 200 OK
    API-->>Repo: success
    Repo-->>Cubit: completion success
    Cubit-->>UI: trigger navigation
    UI->>Router: Navigate back to tests catalog
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰
I hopped through DTOs, routes, and DI,
Two paths now share one cubit’s cry.
Authenticated footsteps, neat and spry,
Tests started, saved, and waved goodbye.
A rabbit cheers — hop, code, and fly! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(tests): add authenticated test attempt flow' clearly and directly describes the main change: adding an authenticated test attempt flow to the tests feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/auth-tests-attempt

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
lib/features/tests/attempt/presentation/pages/tests_attempt_page.dart (1)

101-103: Consider adding a brief delay before navigation for UX polish.

Using Duration.zero navigates immediately on completion. If you want users to briefly see a success state before navigation, consider a small delay (e.g., 300-500ms).

♻️ Optional: add brief delay
        if (state.isCompleted) {
-          unawaited(Future<void>.delayed(Duration.zero, _returnToCatalog));
+          unawaited(Future<void>.delayed(const Duration(milliseconds: 300), _returnToCatalog));
        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/tests/attempt/presentation/pages/tests_attempt_page.dart` around
lines 101 - 103, The immediate navigation when state.isCompleted uses
unawaited(Future<void>.delayed(Duration.zero, _returnToCatalog)); — change the
delay to a short non-zero duration (e.g., Duration(milliseconds: 300) or 500) so
the success state is visible briefly before calling _returnToCatalog; update the
delayed call that wraps _returnToCatalog to use the chosen Duration.
lib/features/tests/attempt/data/mappers/test_attempt_mapper.dart (1)

52-58: Consider renaming extension to match the DTO it operates on.

The extension is named SaveGuestTestResultMapper but operates on the shared SaveTestResultDataDto (not guest-specific). For consistency with the DTO renaming throughout this PR, consider renaming to SaveTestResultMapper.

♻️ Suggested rename
-/// Extension that maps save-result DTOs to [TestAttemptResult].
-extension SaveGuestTestResultMapper on SaveTestResultDataDto {
+/// Extension that maps [SaveTestResultDataDto] to [TestAttemptResult].
+extension SaveTestResultMapper on SaveTestResultDataDto {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/tests/attempt/data/mappers/test_attempt_mapper.dart` around
lines 52 - 58, The extension SaveGuestTestResultMapper is misnamed for its
target type SaveTestResultDataDto; rename the extension to SaveTestResultMapper
so the extension name matches the DTO it operates on and any related refactors
in this PR (update the declaration "extension SaveGuestTestResultMapper on
SaveTestResultDataDto" to "extension SaveTestResultMapper on
SaveTestResultDataDto" and update any references/imports if present).
test/features/tests/attempt/support/test_attempt_dto_fixtures.dart (1)

48-59: Consider renaming fixture to match the returned DTO type.

The function createSaveGuestTestResultResponseDto now returns SaveTestResultResponseDto (not guest-specific), creating a naming mismatch with the PR's DTO renaming convention.

♻️ Suggested rename
-/// Test fixture for save-result response DTO.
-SaveTestResultResponseDto createSaveGuestTestResultResponseDto({
+/// Test fixture for save-result response DTO.
+SaveTestResultResponseDto createSaveTestResultResponseDto({

Note: This would require updating call sites in test files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/features/tests/attempt/support/test_attempt_dto_fixtures.dart` around
lines 48 - 59, Rename the fixture function createSaveGuestTestResultResponseDto
to match the returned DTO name (e.g., createSaveTestResultResponseDto) because
it returns SaveTestResultResponseDto (containing SaveTestResultDataDto); update
the function declaration and all test call sites that reference
createSaveGuestTestResultResponseDto to the new name so naming follows the DTO
renaming convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Around line 30-31: Move the two bullet points about DTO renames and
test-attempt DI wiring into the Unreleased section under a new or existing "###
Breaking" heading: cut the bullets "- Shared test-attempt transport DTOs..." and
"- Test-attempt DI wiring..." and paste them beneath "### Breaking" in the
Unreleased section (create the heading if missing), preserving markdown list
formatting and keeping the rest of the Unreleased content intact so
compatibility-impacting changes are clearly labeled.

In `@lib/features/tests/attempt/data/dto/save_test_result_response_dto.dart`:
- Line 10: Update the stale doc comment on the SaveTestResultResponseDto so it
no longer reads "Guest result payload" and instead describes that this DTO
represents a test result payload shared across multiple flows; locate the class
or typedef named SaveTestResultResponseDto in the file and replace the
single-line summary comment with a concise, accurate description such as "Test
result payload shared across flows" (or equivalent wording).

In
`@test/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl_test.dart`:
- Around line 211-226: The test 'returns UnknownTestsFailure when unexpected
exception occurs' is missing verification that logger.e was called when
apiClient.completeTest throws; update the test for repository.completeTest to
verify logger.e was invoked (matching the pattern used in the startTest
unexpected-exception spec), i.e., after calling repository.completeTest and
asserting failure, add a verify(logger.e(any, any)) (or the exact logger.e
invocation used elsewhere) and verifyNoMoreInteractions(logger) as appropriate
to ensure the unexpected exception is logged.
- Around line 127-143: Test is missing verification that the repository logs the
malformed payload error; update the test for saveResult (the case where
apiClient.saveTestResult returns a malformed response) to also verify that
logger.e was called with the expected error context. Specifically, in the test
surrounding repository.saveResult
(AuthenticatedTestAttemptRepositoryImpl.saveResult scenario), add a verify call
for the mocked logger's e method (or logger.error equivalent) to assert it was
invoked once with an error containing the StateError (or at least any error),
and keep the existing verify(apiClient.saveTestResult(...)). Ensure you
reference the same mock logger instance used by the repository in the test setup
and use verifyNoMoreInteractions on the logger if desired.

---

Nitpick comments:
In `@lib/features/tests/attempt/data/mappers/test_attempt_mapper.dart`:
- Around line 52-58: The extension SaveGuestTestResultMapper is misnamed for its
target type SaveTestResultDataDto; rename the extension to SaveTestResultMapper
so the extension name matches the DTO it operates on and any related refactors
in this PR (update the declaration "extension SaveGuestTestResultMapper on
SaveTestResultDataDto" to "extension SaveTestResultMapper on
SaveTestResultDataDto" and update any references/imports if present).

In `@lib/features/tests/attempt/presentation/pages/tests_attempt_page.dart`:
- Around line 101-103: The immediate navigation when state.isCompleted uses
unawaited(Future<void>.delayed(Duration.zero, _returnToCatalog)); — change the
delay to a short non-zero duration (e.g., Duration(milliseconds: 300) or 500) so
the success state is visible briefly before calling _returnToCatalog; update the
delayed call that wraps _returnToCatalog to use the chosen Duration.

In `@test/features/tests/attempt/support/test_attempt_dto_fixtures.dart`:
- Around line 48-59: Rename the fixture function
createSaveGuestTestResultResponseDto to match the returned DTO name (e.g.,
createSaveTestResultResponseDto) because it returns SaveTestResultResponseDto
(containing SaveTestResultDataDto); update the function declaration and all test
call sites that reference createSaveGuestTestResultResponseDto to the new name
so naming follows the DTO renaming convention.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f0ab3030-b683-4372-bac0-d76e58631b5f

📥 Commits

Reviewing files that changed from the base of the PR and between 773fe38 and cf6b1c0.

📒 Files selected for processing (28)
  • CHANGELOG.md
  • lib/core/di/di.dart
  • lib/core/network/api_paths.dart
  • lib/core/router/router.dart
  • lib/core/router/router_paths.dart
  • lib/features/fitness_start/presentation/pages/fitness_start_test_attempt_page_builder.dart
  • lib/features/tests/attempt/data/dto/complete_guest_test_request_dto.dart
  • lib/features/tests/attempt/data/dto/complete_test_request_dto.dart
  • lib/features/tests/attempt/data/dto/save_guest_test_result_request_dto.dart
  • lib/features/tests/attempt/data/dto/save_guest_test_result_response_dto.dart
  • lib/features/tests/attempt/data/dto/save_test_result_data_dto.dart
  • lib/features/tests/attempt/data/dto/save_test_result_request_dto.dart
  • lib/features/tests/attempt/data/dto/save_test_result_response_dto.dart
  • lib/features/tests/attempt/data/dto/start_test_data_dto.dart
  • lib/features/tests/attempt/data/dto/start_test_response_dto.dart
  • lib/features/tests/attempt/data/mappers/test_attempt_mapper.dart
  • lib/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl.dart
  • lib/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl.dart
  • lib/features/tests/attempt/data/repositories/test_attempt_result_payload_validator.dart
  • lib/features/tests/attempt/domain/repositories/test_attempt_repository.dart
  • lib/features/tests/attempt/presentation/cubits/test_attempt_cubit.dart
  • lib/features/tests/attempt/presentation/pages/tests_attempt_page.dart
  • lib/features/tests/attempt/presentation/pages/tests_attempt_page_builder.dart
  • lib/features/tests/catalog/presentation/pages/tests_catalog_page.dart
  • lib/features/tests/data/remote/tests_api_client.dart
  • test/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl_test.dart
  • test/features/tests/attempt/data/repositories/guest_test_attempt_repository_impl_test.dart
  • test/features/tests/attempt/support/test_attempt_dto_fixtures.dart
💤 Files with no reviewable changes (3)
  • lib/features/tests/attempt/data/dto/complete_guest_test_request_dto.dart
  • lib/features/tests/attempt/data/dto/save_guest_test_result_request_dto.dart
  • lib/features/tests/attempt/data/dto/save_guest_test_result_response_dto.dart

Comment thread CHANGELOG.md
Comment thread lib/features/tests/attempt/data/dto/save_test_result_response_dto.dart Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl_test.dart (1)

82-166: Missing test case for general exception in saveResult.

The saveResult implementation has a catch-all catch (e, s) block (lines 62-64 in the implementation) that handles unexpected exceptions and logs via logger.e, but there's no corresponding test. Both startTest (line 66-79) and completeTest (line 211-227) have this test case for consistency.

💚 Proposed test to add
      test('returns UnknownTestsFailure when unexpected exception occurs', () async {
        final exception = Exception('unexpected_error');
        when(apiClient.saveTestResult(any, any)).thenThrow(exception);

        final result = await repository.saveResult(
          attemptId: '11',
          testingExerciseId: 16,
          resultValue: 2,
        );

        expect(result.isFailure, isTrue);
        expect(result.failure, isA<UnknownTestsFailure>());
        expect(result.failure!.parentException, exception);

        verify(apiClient.saveTestResult(any, any)).called(1);
        verify(logger.e(any, exception, any)).called(1);
        verifyNoMoreInteractions(apiClient);
      });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@test/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl_test.dart`
around lines 82 - 166, Add a test for saveResult to cover the catch-all
exception path: when apiClient.saveTestResult throws a general Exception, assert
the repository.saveResult returns a failure of type UnknownTestsFailure with
parentException equal to that exception, verify apiClient.saveTestResult was
called once, and verify logger.e was called once with the exception; reference
the repository.saveResult method, the apiClient.saveTestResult stub,
UnknownTestsFailure, and logger.e to locate where to add the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@test/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl_test.dart`:
- Around line 82-166: Add a test for saveResult to cover the catch-all exception
path: when apiClient.saveTestResult throws a general Exception, assert the
repository.saveResult returns a failure of type UnknownTestsFailure with
parentException equal to that exception, verify apiClient.saveTestResult was
called once, and verify logger.e was called once with the exception; reference
the repository.saveResult method, the apiClient.saveTestResult stub,
UnknownTestsFailure, and logger.e to locate where to add the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3590b1d9-b106-42b4-94c8-b28203db56b1

📥 Commits

Reviewing files that changed from the base of the PR and between cf6b1c0 and b1b79e0.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • lib/features/tests/attempt/data/dto/save_test_result_response_dto.dart
  • test/features/tests/attempt/data/repositories/authenticated_test_attempt_repository_impl_test.dart
✅ Files skipped from review due to trivial changes (2)
  • lib/features/tests/attempt/data/dto/save_test_result_response_dto.dart
  • CHANGELOG.md

@CowboyGH
CowboyGH merged commit 58d1236 into develop Mar 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: logic State management and business logic area: network API, requests, and data parsing area: ui/ux Widgets, layout, animations, or design breaking-change Changes that break current API or compatibility type: feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant