Skip to content

refactor(onboarding): remove legacy start-trial flow and endpoint - #3569

Open
ygrishajev wants to merge 1 commit into
mainfrom
fix/start-trial-n-old-onboarding
Open

refactor(onboarding): remove legacy start-trial flow and endpoint#3569
ygrishajev wants to merge 1 commit into
mainfrom
fix/start-trial-n-old-onboarding

Conversation

@ygrishajev

@ygrishajev ygrishajev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Why

Closes CON-756

Trial provisioning is fully server-side now — the wallet's created at registration, activation runs off
registration/email-verification, and the UI just polls until it's ready. That leaves the client-side
POST /v1/start-trial call, and the /signup wizard that used it, as dead weight. This drops both.

What

  • Remove POST /v1/start-trial (router, controller shim, schemas, its unused create-UserWallet
    ability) and its only caller — WalletProvider/useManagedWallet are report-only now.
  • Delete the old /signup onboarding wizard; onboarding is the /onboarding picker. Kept
    RequireOnboarding and VerificationCodeInput (still used by passwordless auth); /user/verify-email
    redirects home.

Breaking: /v1/start-trial is gone (ship API + web together); /signup now 404s.

Summary by CodeRabbit

  • Breaking Changes

    • Removed the legacy signup and onboarding flow, including email verification, payment setup, free-trial activation, and template selection.
    • Removed the POST /v1/start-trial API endpoint.
    • New managed wallets can no longer be created through the application; existing wallets remain available for viewing and signing.
    • Standard users can only read and sign their own wallet.
  • Improvements

    • Wallet connection now directs users to the wallet picker.
    • Email verification redirects to the home page.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dcfd3f84-6a36-4ee9-b4d5-8c540d6b9a4c

📥 Commits

Reviewing files that changed from the base of the PR and between 558ca82 and 6ffcae5.

📒 Files selected for processing (4)
  • apps/deploy-web/src/components/shared/PaymentMethodsList/PaymentMethodsList.tsx
  • apps/deploy-web/src/components/shared/PaymentMethodsList/index.ts
  • apps/deploy-web/src/hooks/useReturnTo/index.ts
  • apps/deploy-web/src/hooks/useTrialBalance.ts
💤 Files with no reviewable changes (4)
  • apps/deploy-web/src/components/shared/PaymentMethodsList/index.ts
  • apps/deploy-web/src/hooks/useReturnTo/index.ts
  • apps/deploy-web/src/components/shared/PaymentMethodsList/PaymentMethodsList.tsx
  • apps/deploy-web/src/hooks/useTrialBalance.ts

📝 Walkthrough

Walkthrough

The change removes the legacy trial-start API and managed-wallet creation flow. It deletes the related onboarding UI, updates wallet state to use retrieval only, and changes verification and wallet connection navigation.

Changes

Legacy trial API removal

Layer / File(s) Summary
API contract and controller removal
apps/api/src/auth/services/ability/ability.service.ts, apps/api/src/billing/controllers/..., apps/api/src/billing/http-schemas/..., apps/api/src/billing/routes/..., apps/api/src/routers/open-api-handlers.ts, apps/api/swagger/openapi.json, packages/console-api-types/src/schema.d.ts
Regular users retain wallet read and sign permissions but lose create. The wallet controller, /v1/start-trial route, schemas, handlers, and API contracts no longer expose wallet creation.

Managed-wallet retrieval state

Layer / File(s) Summary
Query, hook, provider, and service cleanup
apps/deploy-web/src/hooks/useManagedWallet.ts, apps/deploy-web/src/queries/..., apps/deploy-web/src/context/WalletProvider/WalletProvider.tsx, packages/http-sdk/src/managed-wallet-http/..., apps/deploy-web/src/services/app-di-container/...
Managed-wallet creation mutations, cache keys, HTTP methods, creation errors, and trial analytics are removed. Wallet state now comes from the wallet query.
Wallet state validation
apps/deploy-web/src/hooks/useManagedWallet.spec.tsx, apps/deploy-web/src/queries/useManagedWalletQuery.spec.tsx
Tests now cover wallet retrieval with and without a user ID and remove creation-mutation coverage.

Onboarding removal and navigation

Layer / File(s) Summary
Legacy onboarding deletion
apps/deploy-web/src/components/onboarding/OnboardingPage.tsx, apps/deploy-web/src/components/onboarding/OnboardingContainer/*, apps/deploy-web/src/components/onboarding/OnboardingView/*, apps/deploy-web/src/components/onboarding/OnboardingStepper/*, apps/deploy-web/src/components/onboarding/steps/*, apps/deploy-web/src/pages/signup/index.tsx
The legacy onboarding, signup, email verification, payment, trial, welcome, and supporting components and tests are deleted.
Redirect and route updates
apps/deploy-web/src/components/onboarding/VerifyEmailPage/*, apps/deploy-web/src/components/wallet/ConnectManagedWalletButton.tsx, apps/deploy-web/src/hooks/useOnboardingChrome.ts, apps/deploy-web/src/hooks/useReturnTo/*, apps/deploy-web/src/utils/urlUtils.ts, apps/deploy-web/tests/ui/pages/HomePage.ts
Email verification redirects to home. Managed-wallet connection opens the onboarding picker. Signup is no longer classified as onboarding.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: baktun14

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/start-trial-n-old-onboarding

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

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
4878 2 4876 5
View the top 1 failed test(s) by shortest run time
test/functional/docs.spec.ts > API Docs > GET /v1/doc > returns docs with all routes expected
Stack Traces | 0.281s run time
Error: Snapshot `API Docs > GET /v1/doc > returns docs with all routes expected 1` mismatched

- Expected
+ Received

@@ -13371,122 +13371,10 @@
          "tags": [
            "Users",
          ],
        },
      },
-     "/v1/start-trial": {
-       "post": {
-         "description": "Ensures the user's managed wallet exists and enqueues background trial activation. Kept for backward compatibility; trial activation now runs server-side off registration/verification.",
-         "requestBody": {
-           "content": {
-             "application/json": {
-               "schema": {
-                 "properties": {
-                   "data": {
-                     "properties": {
-                       "userId": {
-                         "type": "string",
-                       },
-                     },
-                     "required": [
-                       "userId",
-                     ],
-                     "type": "object",
-                   },
-                 },
-                 "required": [
-                   "data",
-                 ],
-                 "type": "object",
-               },
-             },
-           },
-         },
-         "responses": {
-           "200": {
-             "content": {
-               "application/json": {
-                 "schema": {
-                   "properties": {
-                     "data": {
-                       "additionalProperties": false,
-                       "properties": {
-                         "address": {
-                           "nullable": true,
-                           "type": "string",
-                         },
-                         "clientSecret": {
-                           "nullable": true,
-                           "type": "string",
-                         },
-                         "createdAt": {
-                           "nullable": true,
-                           "type": "string",
-                         },
-                         "creditAmount": {
-                           "type": "number",
-                         },
-                         "denom": {
-                           "type": "string",
-                         },
-                         "id": {
-                           "nullable": true,
-                           "type": "number",
-                         },
-                         "isTrialing": {
-                           "type": "boolean",
-                         },
-                         "paymentIntentId": {
-                           "nullable": true,
-                           "type": "string",
-                         },
-                         "paymentMethodId": {
-                           "nullable": true,
-                           "type": "string",
-                         },
-                         "requires3DS": {
-                           "type": "boolean",
-                         },
-                         "topUpMinAmountUsd": {
-                           "description": "Minimum USD amount accepted by the next paid top-up for this wallet.",
-                           "type": "number",
-                         },
-                         "userId": {
-                           "nullable": true,
-                           "type": "string",
-                         },
-                       },
-                       "required": [
-                         "id",
-                         "userId",
-                         "creditAmount",
-                         "address",
-                         "denom",
-                         "isTrialing",
-                         "topUpMinAmountUsd",
-                         "createdAt",
-                       ],
-                       "type": "object",
-                     },
-                   },
-                   "required": [
-                     "data",
-                   ],
-                   "type": "object",
-                 },
-               },
-             },
-             "description": "Wallet ensured and trial activation enqueued",
-           },
-         },
-         "security": [],
-         "summary": "Start a trial period for a user",
-         "tags": [
-           "Wallet",
-         ],
-       },
-     },
      "/v1/templates-list": {
        "get": {
          "responses": {
            "200": {
              "content": {

 ❯ test/functional/docs.spec.ts:15:20
View the full list of 1 ❄️ flaky test(s)
test/functional/docs.spec.ts > test/functional/docs.spec.ts

Flake rate in main: 100.00% (Passed 0 times, Failed 8 times)

Stack Traces | 20.8s run time
Error: Hook timed out in 20000ms.
If this is a long-running hook, pass a timeout value as the last argument or configure it globally with "hookTimeout".
 ❯ test/setup-functional-tests.ts:44:1

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@ygrishajev
ygrishajev force-pushed the fix/start-trial-n-old-onboarding branch from 558ca82 to c7c895d Compare August 7, 2026 09:51
Trial wallet provisioning now runs server-side (the wallet is ensured at registration and
activation is dispatched off registration/email-verification) and the UI polls the wallet
until it is ready, so the client-triggered start-trial path is redundant.

Remove the POST /v1/start-trial endpoint together with its controller shim, request/response
schemas, and the create-UserWallet ability, and remove the client that called it
(ManagedWalletHttpService.createWallet, useCreateManagedWalletMutation), slimming
WalletProvider and useManagedWallet to reporting-only.

Remove the superseded /signup onboarding wizard and its steps; the onboarding entry is now the
/onboarding picker gated by RequireOnboarding. Keep VerificationCodeInput (shared with
passwordless auth) and repoint /user/verify-email to home.
@ygrishajev
ygrishajev force-pushed the fix/start-trial-n-old-onboarding branch from c7c895d to 6ffcae5 Compare August 7, 2026 09:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/deploy-web/src/components/wallet/ConnectManagedWalletButton.tsx (1)

29-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a click-navigation assertion.

ConnectManagedWalletButton.spec.tsx tests only enabled and disabled states. Add a test that verifies router.push receives UrlService.onboardingPicker(). This protects the changed destination from regression.

🤖 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 `@apps/deploy-web/src/components/wallet/ConnectManagedWalletButton.tsx` around
lines 29 - 31, Extend ConnectManagedWalletButton.spec.tsx with an enabled-button
click test that invokes the handler and asserts router.push receives
UrlService.onboardingPicker(). Reuse the existing router mock and button setup
while preserving the current enabled and disabled state tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts`:
- Around line 64-68: Update the AuthService mock setup around the rootContainer
registration: create the mock without the inline ability override, then assign
the real createMongoAbility result to its ability property afterward so CASL
internals are not recursively mocked. Ensure authorization assertions use a
throwUnlessCan implementation that delegates to the assigned ability when
needed.

---

Nitpick comments:
In `@apps/deploy-web/src/components/wallet/ConnectManagedWalletButton.tsx`:
- Around line 29-31: Extend ConnectManagedWalletButton.spec.tsx with an
enabled-button click test that invokes the handler and asserts router.push
receives UrlService.onboardingPicker(). Reuse the existing router mock and
button setup while preserving the current enabled and disabled state tests.
🪄 Autofix

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: 167fa368-db2a-4ca7-ac3d-870ac472a31e

📥 Commits

Reviewing files that changed from the base of the PR and between 7e19b74 and 558ca82.

📒 Files selected for processing (55)
  • apps/api/src/auth/services/ability/ability.service.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts
  • apps/api/src/billing/controllers/wallet/wallet.controller.ts
  • apps/api/src/billing/http-schemas/wallet.schema.ts
  • apps/api/src/billing/routes/index.ts
  • apps/api/src/billing/routes/start-trial/start-trial.router.ts
  • apps/api/src/routers/open-api-handlers.ts
  • apps/api/swagger/openapi.json
  • apps/deploy-web/src/components/deployments/DeploymentDetailTopBar/DeploymentDetailTopBar.spec.tsx
  • apps/deploy-web/src/components/deployments/ManifestUpdate/ManifestUpdate.spec.tsx
  • apps/deploy-web/src/components/home/YourAccount/YourAccount.spec.tsx
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx
  • apps/deploy-web/src/components/onboarding/OnboardingPage.tsx
  • apps/deploy-web/src/components/onboarding/OnboardingStepper/OnboardingStepper.tsx
  • apps/deploy-web/src/components/onboarding/OnboardingView/OnboardingView.tsx
  • apps/deploy-web/src/components/onboarding/VerifyEmailPage/VerifyEmailPage.spec.tsx
  • apps/deploy-web/src/components/onboarding/VerifyEmailPage/VerifyEmailPage.tsx
  • apps/deploy-web/src/components/onboarding/steps/EmailVerificationContainer/EmailVerificationContainer.spec.tsx
  • apps/deploy-web/src/components/onboarding/steps/EmailVerificationContainer/EmailVerificationContainer.tsx
  • apps/deploy-web/src/components/onboarding/steps/EmailVerificationStep/EmailVerificationStep.spec.tsx
  • apps/deploy-web/src/components/onboarding/steps/EmailVerificationStep/EmailVerificationStep.tsx
  • apps/deploy-web/src/components/onboarding/steps/FreeTrialLandingStep/FreeTrialLandingStep.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.spec.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodStep/PaymentMethodStep.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/EmptyPaymentMethods.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/ErrorAlert.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/PaymentMethodsDisplay.test.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/PaymentMethodsDisplay.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/TermsAndConditions.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/TrialStartButton.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentVerificationCard/PaymentVerificationCard.tsx
  • apps/deploy-web/src/components/onboarding/steps/WelcomeStep/TemplateCard.tsx
  • apps/deploy-web/src/components/onboarding/steps/WelcomeStep/TrialStatusBar.tsx
  • apps/deploy-web/src/components/onboarding/steps/WelcomeStep/WelcomeStep.tsx
  • apps/deploy-web/src/components/wallet/ConnectManagedWalletButton.tsx
  • apps/deploy-web/src/context/WalletProvider/WalletProvider.tsx
  • apps/deploy-web/src/hooks/useManagedWallet.spec.tsx
  • apps/deploy-web/src/hooks/useManagedWallet.ts
  • apps/deploy-web/src/hooks/useOnboardingChrome.spec.ts
  • apps/deploy-web/src/hooks/useOnboardingChrome.ts
  • apps/deploy-web/src/hooks/useReturnTo/useReturnTo.ts
  • apps/deploy-web/src/pages/signup/index.tsx
  • apps/deploy-web/src/queries/queryKeys.ts
  • apps/deploy-web/src/queries/useManagedWalletQuery.spec.tsx
  • apps/deploy-web/src/queries/useManagedWalletQuery.ts
  • apps/deploy-web/src/services/analytics/analytics.service.ts
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts
  • apps/deploy-web/src/utils/urlUtils.ts
  • apps/deploy-web/tests/seeders/wallet.ts
  • apps/deploy-web/tests/ui/pages/HomePage.ts
  • apps/deploy-web/tests/ui/pages/OnboardingPage.ts
  • packages/console-api-types/src/schema.d.ts
  • packages/http-sdk/src/managed-wallet-http/managed-wallet-http.service.ts
💤 Files with no reviewable changes (39)
  • apps/deploy-web/src/components/home/YourAccount/YourAccount.spec.tsx
  • apps/api/src/billing/routes/index.ts
  • apps/deploy-web/src/components/onboarding/steps/WelcomeStep/TrialStatusBar.tsx
  • apps/deploy-web/src/components/deployments/ManifestUpdate/ManifestUpdate.spec.tsx
  • apps/deploy-web/src/components/onboarding/steps/WelcomeStep/TemplateCard.tsx
  • apps/deploy-web/src/components/onboarding/OnboardingPage.tsx
  • apps/deploy-web/src/pages/signup/index.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/ErrorAlert.tsx
  • apps/deploy-web/tests/seeders/wallet.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
  • apps/api/swagger/openapi.json
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.spec.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/TermsAndConditions.tsx
  • apps/api/src/routers/open-api-handlers.ts
  • packages/http-sdk/src/managed-wallet-http/managed-wallet-http.service.ts
  • apps/deploy-web/src/components/onboarding/steps/EmailVerificationContainer/EmailVerificationContainer.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodStep/PaymentMethodStep.tsx
  • apps/deploy-web/src/components/onboarding/steps/WelcomeStep/WelcomeStep.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/PaymentMethodsDisplay.tsx
  • apps/deploy-web/src/components/onboarding/steps/FreeTrialLandingStep/FreeTrialLandingStep.tsx
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx
  • apps/deploy-web/src/utils/urlUtils.ts
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodContainer/PaymentMethodContainer.tsx
  • packages/console-api-types/src/schema.d.ts
  • apps/deploy-web/tests/ui/pages/OnboardingPage.ts
  • apps/deploy-web/src/components/onboarding/OnboardingStepper/OnboardingStepper.tsx
  • apps/deploy-web/src/services/analytics/analytics.service.ts
  • apps/deploy-web/src/queries/queryKeys.ts
  • apps/deploy-web/src/components/deployments/DeploymentDetailTopBar/DeploymentDetailTopBar.spec.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/PaymentMethodsDisplay.test.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/TrialStartButton.tsx
  • apps/api/src/billing/routes/start-trial/start-trial.router.ts
  • apps/deploy-web/src/components/onboarding/steps/PaymentMethodsDisplay/EmptyPaymentMethods.tsx
  • apps/deploy-web/src/components/onboarding/steps/EmailVerificationStep/EmailVerificationStep.spec.tsx
  • apps/api/src/billing/http-schemas/wallet.schema.ts
  • apps/deploy-web/src/components/onboarding/OnboardingView/OnboardingView.tsx
  • apps/deploy-web/src/components/onboarding/steps/EmailVerificationStep/EmailVerificationStep.tsx
  • apps/deploy-web/src/components/onboarding/steps/PaymentVerificationCard/PaymentVerificationCard.tsx
  • apps/deploy-web/src/components/onboarding/steps/EmailVerificationContainer/EmailVerificationContainer.spec.tsx

Comment on lines 64 to 68
rootContainer.register(AuthService, {
useValue: mock<AuthService>({
ability: createMongoAbility<MongoAbility>([{ action: "create", subject: "UserWallet" }]),
ability: createMongoAbility<MongoAbility>([{ action: "read", subject: "UserWallet" }]),
currentUser: input?.user ?? createUser()
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- CLAUDE files ---'
find .. -name CLAUDE.md -print

printf '%s\n' '--- target test file ---'
sed -n '1,150p' apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts

printf '%s\n' '--- mock<AuthService> usage ---'
rg -n -C 3 'mock<AuthService>|createMongoAbility|rootContainer.register\(AuthService' apps/api/src/billing apps/api/src | head -240

printf '%s\n' '--- dependency declarations ---'
rg -n -C 2 'vitest-mock-extended|vitest' package.json apps/api/package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null | head -160

Repository: akash-network/console

Length of output: 36565


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- repository guidance ---'
cat CLAUDE.md

printf '%s\n' '--- AuthService ability declaration and access ---'
rg -n -C 5 'ability|currentUser' apps/api/src/auth/services/auth.service.ts apps/api/src/billing/controllers/wallet/wallet.controller.ts apps/api/src/billing/services/wallet-reader/wallet-reader.service.ts

printf '%s\n' '--- mock library implementation ---'
find node_modules/vitest-mock-extended -maxdepth 3 -type f -print | sort | head -80
rg -n -C 8 'function mock|const mock|mock<' node_modules/vitest-mock-extended/dist node_modules/vitest-mock-extended/lib 2>/dev/null | head -240

printf '%s\n' '--- relevant package metadata ---'
node -e 'const p=require("./node_modules/vitest-mock-extended/package.json"); console.log(JSON.stringify({version:p.version,main:p.main,exports:p.exports},null,2))'

Repository: akash-network/console

Length of output: 17353


🌐 Web query:

vitest-mock-extended v4 mock nested object overrides implementation

💡 Result:

In vitest-mock-extended, you can override implementations for nested objects or methods by using the mockDeep<T>() function and providing a partial implementation [1][2]. To override nested properties while maintaining deep mocking capabilities: 1. Pass your partial implementation object directly into the mockDeep<T>() function [1]. 2. The library internally uses an overrideMockImp function that recursively applies these provided implementations to the object structure [1][3]. 3. For properties you do not explicitly define in your override, the deep proxy handler will continue to automatically generate mock implementations (or use a fallbackMockImplementation if provided) when those properties are accessed [1][2][4]. Example pattern: const myMock = mockDeep({ nested: { property: 'overridden-value', method: vi.fn.mockReturnValue('custom-result') } }); In this example, calling myMock.nested.property will return 'overridden-value', while other unmocked properties or nested objects within myMock will remain automatically mocked via the deep proxy system [1][4]. If you need to provide a default behavior for all unmocked methods, you can pass a fallbackMockImplementation in the options object [1][2][4]: const myMock = mockDeep({ // options }, { fallbackMockImplementation: (propName) =>... }); The internal overrideMockImp ensures that your custom defined structure is preserved while the remaining graph is automatically filled with proxies [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- installed dependency locations ---'
find . -path '*/node_modules/vitest-mock-extended/package.json' -print

printf '%s\n' '--- package-lock entries ---'
sed -n '3980,4045p' package-lock.json
rg -n -C 3 '"vitest-mock-extended"' package-lock.json

printf '%s\n' '--- AuthService complete implementation ---'
cat -n apps/api/src/auth/services/auth.service.ts

printf '%s\n' '--- WalletController implementation ---'
cat -n apps/api/src/billing/controllers/wallet/wallet.controller.ts

printf '%s\n' '--- repository test patterns assigning ability ---'
rg -n -C 5 'const .*authService = mock<AuthService>|authService\.ability|mock<AuthService>\(\{[[:space:]]*$' apps/api/src --glob '*.spec.ts' | head -320

Repository: akash-network/console

Length of output: 42108


🌐 Web query:

site:github.com/eratio08/vitest-mock-extended/blob v4 src Mock.ts mock overrideMockImp

💡 Result:

In the context of the vitest-mock-extended library, overrideMockImp is an internal utility function located in src/Mock.ts [1]. It is used to recursively construct a proxy-based mock object [1]. The function iterates over the properties of the provided obj and applies a proxy handler [1]. If a property is an object (and not null), overrideMockImp is called recursively to ensure deep mocking [1]. Otherwise, the property value is assigned directly to the proxy [1]. This mechanism is fundamental to how mock() and mockDeep() generate objects that support property access and function spying while maintaining type safety [1][2][3]. While overrideMockImp is not typically intended for direct use by consumers of the library, it supports the library's ability to recursively mock complex structures and handle custom mock implementations [1][4]. Users typically interact with this functionality through the public mock() or mockDeep() APIs [5][2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT

curl -fsSL 'https://raw.githubusercontent.com/eratio08/vitest-mock-extended/c12fe299/src/Mock.ts' -o "$tmpdir/Mock.ts"
curl -fsSL 'https://raw.githubusercontent.com/eratio08/vitest-mock-extended/c12fe299/src/Mock.spec.ts' -o "$tmpdir/Mock.spec.ts"

printf '%s\n' '--- mock implementation ---'
cat "$tmpdir/Mock.ts"

printf '%s\n' '--- nested override tests ---'
rg -n -C 8 'override|nested|mockDeep|mock<' "$tmpdir/Mock.spec.ts" | head -320

Repository: akash-network/console

Length of output: 19403


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- CASL dependency locations ---'
find . -path '*/node_modules/@casl/ability/package.json' -print

printf '%s\n' '--- CASL versions in lockfile ---'
rg -n -C 4 '"`@casl/ability`"|casl-ability' package-lock.json apps/api/package.json

printf '%s\n' '--- npm cache entries for relevant packages ---'
find ~/.npm -type f \( -iname '*casl*' -o -iname '*vitest-mock-extended*' \) -print 2>/dev/null | head -80

printf '%s\n' '--- source references to ability behavior ---'
rg -n -C 5 'ability\.(can|rules|update|cannot)|accessibleBy\(.*ability' apps/api/src --glob '*.spec.ts' --glob '*.ts' | head -320

Repository: akash-network/console

Length of output: 35400


🌐 Web query:

CASL ability v6 createMongoAbility AbilityBuilder source rules own properties can method

💡 Result:

In CASL v6, the AbilityBuilder is a utility used to declaratively define authorization rules [1][2]. It works by wrapping a factory function, such as createMongoAbility, to produce an ability instance [1][3]. To define rules, you use the can and cannot methods provided by the builder [1][3]. When defining rules for objects that belong to a specific user (e.g., "own" properties), you typically pass a conditions object as the third argument to the can method [3]. Example of defining permissions for "own" resources: import { AbilityBuilder, createMongoAbility } from '@casl/ability'; function defineAbilitiesFor(user) { const { can, build } = new AbilityBuilder(createMongoAbility); // Allow managing posts where the author's id matches the user's id can('manage', 'BlogPost', { author: user.id }); return build; } In this pattern: 1. createMongoAbility: Used to initialize the ability instance with MongoDB-style condition matching [4][3]. 2. AbilityBuilder: Provides the can and cannot methods [1][3]. 3. Conditions: When you pass an object like { author: user.id }, CASL interprets this as a condition to be matched against the subject when you later call ability.can('manage', subject) [3]. The library checks if the property of the subject (e.g., subject.author) matches the value provided in your rule (e.g., user.id) [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT

for file in Ability.ts PureAbility.ts RuleIndex.ts; do
  curl -fsSL "https://raw.githubusercontent.com/stalniy/casl/v6.7.3/packages/casl-ability/src/$file" -o "$tmpdir/$file" || true
done

printf '%s\n' '--- CASL source files retrieved ---'
ls -l "$tmpdir"

printf '%s\n' '--- CASL constructors and methods ---'
rg -n -C 12 'constructor|can\(|rules|actions|subjects|merged|createMongoAbility' "$tmpdir" | head -500

Repository: akash-network/console

Length of output: 388


🏁 Script executed:

#!/bin/bash
set -eu

tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT

for file in Ability.ts PureAbility.ts RuleIndex.ts; do
  url="https://raw.githubusercontent.com/stalniy/casl/a94cdcbb/packages/casl-ability/src/$file"
  if curl -fsSL "$url" -o "$tmpdir/$file"; then
    printf 'retrieved %s\n' "$file"
  fi
done

printf '%s\n' '--- retrieved CASL source ---'
for file in "$tmpdir"/*; do
  printf '\n--- %s ---\n' "$file"
  cat "$file"
done

Repository: akash-network/console

Length of output: 13816


🏁 Script executed:

#!/bin/bash
set -eu

node <<'JS'
"use strict";

// Minimal standalone reproduction of vitest-mock-extended's mock()/overrideMockImp
// behavior and CASL's Map-backed ability access. This does not load repository code.
function mockFunction() {
  const fn = function () {};
  fn._isMockFunction = true;
  return fn;
}

const handler = {
  ownKeys(target) {
    return Reflect.ownKeys(target);
  },
  set(target, property, value) {
    target[property] = value;
    return true;
  },
  get(target, property) {
    if (!(property in target)) {
      if (property === "_isMockObject" || property === "_isMockFunction") return undefined;
      target[property] = mockFunction();
    }
    return target[property];
  }
};

function overrideMockImp(obj) {
  const proxy = new Proxy(obj, handler);
  for (const name of Object.keys(obj)) {
    if (typeof obj[name] === "object" && obj[name] !== null) {
      proxy[name] = overrideMockImp(obj[name]);
    } else {
      proxy[name] = obj[name];
    }
  }
  return proxy;
}

function mock(implementation = {}) {
  implementation._isMockObject = true;
  return overrideMockImp(implementation);
}

class AbilityLike {
  constructor() {
    this.index = new Map([["read:UserWallet", true]]);
  }

  can(action, subject) {
    return this.index.get(`${action}:${subject}`) === true;
  }
}

const ability = new AbilityLike();
const nested = mock({ ability });
const assigned = mock({});
assigned.ability = ability;

for (const [label, value] of [["nested override", nested.ability], ["post-assignment", assigned.ability]]) {
  try {
    console.log(label, "=>", value.can("read", "UserWallet"));
  } catch (error) {
    console.log(label, "=>", `${error.name}: ${error.message}`);
  }
}
JS

Repository: akash-network/console

Length of output: 347


🏁 Script executed:

#!/bin/bash
set -eu

node <<'JS'
"use strict";

function mockFunction() {
  return function () {};
}

const handler = {
  ownKeys: target => Reflect.ownKeys(target),
  set(target, property, value) {
    target[property] = value;
    return true;
  },
  get(target, property) {
    if (!(property in target)) {
      if (property === "_isMockObject" || property === "_isMockFunction") return undefined;
      target[property] = mockFunction();
    }
    return target[property];
  }
};

function overrideMockImp(obj) {
  const proxy = new Proxy(obj, handler);
  for (const name of Object.keys(obj)) {
    proxy[name] =
      typeof obj[name] === "object" && obj[name] !== null
        ? overrideMockImp(obj[name])
        : obj[name];
  }
  return proxy;
}

function mock(implementation = {}) {
  implementation._isMockObject = true;
  return overrideMockImp(implementation);
}

class AbilityLike {
  constructor() {
    this.index = new Map([["read:UserWallet", true]]);
  }
  can(action, subject) {
    return this.index.get(`${action}:${subject}`) === true;
  }
}

const nestedAbility = new AbilityLike();
const nested = mock({ ability: nestedAbility });

const assigned = mock({});
assigned.ability = new AbilityLike();

for (const [label, value] of [
  ["nested override", nested.ability],
  ["post-assignment", assigned.ability]
]) {
  try {
    console.log(label, "=>", value.can("read", "UserWallet"));
  } catch (error) {
    console.log(label, "=>", `${error.name}: ${error.message}`);
  }
}
JS

Repository: akash-network/console

Length of output: 277


Assign ability after creating the AuthService mock.

mock() recursively wraps object-valued overrides. Passing createMongoAbility(...) inline wraps CASL's internal Map, so ability.can(...) can throw a TypeError. Assign the ability after creating the mock. If this test must cover authorization, make throwUnlessCan delegate to that ability because the current auto-mocked method bypasses CASL.

🤖 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 `@apps/api/src/billing/controllers/wallet/wallet.controller.spec.ts` around
lines 64 - 68, Update the AuthService mock setup around the rootContainer
registration: create the mock without the inline ability override, then assign
the real createMongoAbility result to its ability property afterward so CASL
internals are not recursively mocked. Ensure authorization assertions use a
throwUnlessCan implementation that delegates to the assigned ability when
needed.

Source: Learnings

Comment on lines 54 to 59
import { validatorsRouter } from "@src/validator";

export const openApiHonoHandlers: OpenApiHonoHandler[] = [
startTrialRouter,
getWalletListRouter,
walletSettingRouter,
signAndBroadcastTxRouter,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Removing the /v1/start-trial route (via open-api-handlers.ts) changes the generated /v1/doc?scope=console output, but the checked-in functional test snapshot at apps/api/test/functional/snapshots/docs.spec.ts.snap (~line 13379) still contains the old /v1/start-trial path entry. Since that snapshot file wasn't regenerated in this PR, docs.spec.ts's toMatchSnapshot() assertion will fail in CI until it's updated with vitest -u.

Extended reasoning...

apps/api/test/functional/docs.spec.ts issues a GET /v1/doc?scope=console request and asserts the response body against a Jest/Vitest snapshot stored at apps/api/test/functional/__snapshots__/docs.spec.ts.snap via expect(data).toMatchSnapshot(). That endpoint's output is built dynamically from the OpenAPI definitions registered on the openApiHonoHandlers array in apps/api/src/routers/open-api-handlers.ts — it is not sourced from the static swagger/openapi.json file.

This PR removes startTrialRouter from that array (and deletes apps/api/src/billing/routes/start-trial/start-trial.router.ts entirely), so once the app boots with these changes, the OpenAPI document generated for the console scope will no longer contain a /v1/start-trial path. However, the committed snapshot file was not touched by this PR (it does not appear in the changed-files list), and it still contains a full /v1/start-trial path block, including the specific backward-compat description text ("Ensures the user's managed wallet exists and enqueues background trial activation...") that only existed on the now-deleted route.

Concretely, here's how this manifests in CI:

  1. The functional test suite boots the API and calls GET /v1/doc?scope=console.
  2. The response is serialized as JSON and compared with toMatchSnapshot() against the stored .snap file.
  3. The live response omits /v1/start-trial (since startTrialRouter is no longer registered), while the snapshot still expects it present.
  4. Vitest reports a snapshot mismatch, failing docs.spec.ts and, by extension, the functional test job in CI.

Nothing in the existing test setup guards against this — snapshot tests only catch API surface drift if the snapshot is kept in sync, and there's no independent check verifying route registration against the snapshot content. The fix is straightforward: regenerate the snapshot (e.g. cd apps/api && npx vitest run test/functional/docs.spec.ts -u, or the project's test:functional -- -u equivalent) and commit the updated .snap file alongside this PR's other route-removal changes, so CI reflects the new, smaller API surface.

This is a concrete, reproducible functional-test failure that will block CI on this PR as-is, not a stylistic or documentation nit, so it should be fixed before merge.

Comment on lines 15 to 17
async startFreeTrial() {
await this.page.getByRole("button", { name: /start free trial/i }).click();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 OnboardingPage.startFreeTrial() (still calls getByRole('button', {name: /start free trial/i})) targets a button that only ever existed in FreeTrialLandingStep.tsx, which this PR deletes along with the entire /signup wizard. The new /onboarding picker has no such button, so registerWithEmailPassword in tests/ui/actions/auth.ts:113 (the password-signup E2E flow), which calls startFreeTrial() right after signup, will hang until Playwright's action timeout and fail. Please remove startFreeTrial() from OnboardingPage.ts and drop the now-unnecessary call in auth.ts.

Extended reasoning...

The bug: apps/deploy-web/tests/ui/pages/OnboardingPage.ts still exposes startFreeTrial():

async startFreeTrial() {
  await this.page.getByRole("button", { name: /start free trial/i }).click();
}

That locator only ever matched one element in the app: the <Button ... onClick={onStartTrial}>Start Free Trial</Button> rendered by FreeTrialLandingStep.tsx. This PR deletes FreeTrialLandingStep.tsx outright, along with the rest of the /signup onboarding wizard (OnboardingContainer, OnboardingView, OnboardingStepper, the pages/signup/index.tsx route, etc.) — trial provisioning is now handled server-side off registration/verification, and the client-side "click start trial" step no longer exists anywhere in the product.

Where it breaks: apps/deploy-web/tests/ui/actions/auth.ts (untouched by this PR) still drives the password-signup E2E flow through registerWithEmailPassword:

await auth.signUp({ email, password: generateTestPassword() });
await signupResponse;
...
await new OnboardingPage(page).startFreeTrial();   // line 113
await deps.emailVerification.verify(...);

Why nothing catches this today: the deleted component and the leftover page-object method live in different files, so no compiler or lint pass connects them — a Playwright getByRole locator is just a runtime string match against the DOM, not a typed reference to a component. TypeScript happily compiles OnboardingPage.ts since startFreeTrial() has no dependency on the deleted files. The break only manifests when the E2E suite actually runs this flow against a live app.

Concrete walkthrough of the failure:

  1. registerWithEmailPassword submits the password signup form and awaits the /api/auth/password-signup response.
  2. Immediately after, it calls new OnboardingPage(page).startFreeTrial().
  3. Post-PR, the user lands on the new /onboarding picker (OnboardingPickerPage.tsx), whose only actions are a "Deploy image" link, an add-credits ghost button, and SkipOnboardingButton — none of which match /start free trial/i.
  4. Playwright's getByRole('button', { name: /start free trial/i }).click() finds zero matching elements and retries until the default action timeout is hit, failing the test with a timeout error rather than a clear "element not found" message.
  5. The rest of registerWithEmailPassword (email verification, URL assertion) never executes, so the whole password-signup registration E2E path is broken.

Supporting evidence the PR did intend to clean up trial-related page objects: HomePage.ts in this same diff removes its own startTrial() helper and updates isCurrentPage()'s regex from /^\/(onboarding|signup)/ to /^\/onboarding/, showing the author was actively pruning stale trial-flow test helpers — OnboardingPage.startFreeTrial() and its caller were simply missed.

Fix: delete startFreeTrial() from OnboardingPage.ts and remove the now-pointless await new OnboardingPage(page).startFreeTrial(); call in auth.ts's registerWithEmailPassword (trial provisioning happens automatically server-side now, so no client action is needed at that point in the flow).

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant