Skip to content

feat(auth): enhance API key management with new hooks and tests#1813

Merged
baktun14 merged 4 commits intomainfrom
fix/api-keys-no-wallet
Aug 14, 2025
Merged

feat(auth): enhance API key management with new hooks and tests#1813
baktun14 merged 4 commits intomainfrom
fix/api-keys-no-wallet

Conversation

@baktun14
Copy link
Contributor

@baktun14 baktun14 commented Aug 14, 2025

#1798

Summary by CodeRabbit

  • Bug Fixes

    • API Keys list correctly shows an empty state for trialing or unmanaged wallets; related actions are properly gated.
    • Payment prompt now closes automatically when navigating to Add Funds.
  • Tests

    • Added comprehensive tests covering API key fetch, create, and delete flows.
  • Chores

    • Added reusable test data builders and consolidated test seed exports.
  • Refactor

    • Improved type safety for the popup context hook.

@baktun14 baktun14 requested a review from a team as a code owner August 14, 2025 14:12
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 14, 2025

Walkthrough

Adds wallet-managed gating to API key queries and UI; introduces dependency injection for API key hooks; changes paywall actions to receive a close callback; adds tests and seeders for API keys and wallets; and adds an explicit return type for usePopup.

Changes

Cohort / File(s) Summary
API keys hooks + DI
apps/deploy-web/src/queries/useApiKeysQuery.ts
Export USE_API_KEYS_DEPENDENCIES; make useUserApiKeys, useCreateApiKey, useDeleteApiKey accept optional dependencies; useUserApiKeys enable now requires isManaged, user exists, and not isTrialing; hooks use injected useUser/useWallet.
API keys UI
apps/deploy-web/src/components/api-keys/ApiKeyList.tsx
Destructure isManaged from useWallet; extend empty-state condition to show "No API keys found" when no keys, when user is trialing, or when wallet is not managed.
Paywall action handling
apps/deploy-web/src/hooks/usePayingCustomerRequiredEventHandler.tsx
Change actions prop to a function receiving { close }; “Add Funds” action navigates via router.push(UrlService.payment()) and calls close() after navigation.
Tests: API keys
apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
Add Jest tests for useUserApiKeys, useCreateApiKey, useDeleteApiKey covering gating (user/isTrialing/isManaged), fetch/create/delete flows, cache updates, onSuccess callback, and DI via USE_API_KEYS_DEPENDENCIES.
Test seeders
apps/deploy-web/tests/seeders/*
Add apiKey and wallet builders: apps/deploy-web/tests/seeders/apiKey.ts (buildApiKey, buildApiKeys) and apps/deploy-web/tests/seeders/wallet.ts (buildWallet); add barrel apps/deploy-web/tests/seeders/index.ts.
Popup typing
packages/ui/context/PopupProvider/PopupProvider.tsx
Add explicit return type PopupProviderContext to usePopup (type annotation only).

Sequence Diagram(s)

sequenceDiagram
  participant UI as ApiKeyList
  participant Hooks as useUserApiKeys / Mutations
  participant Wallet as useWallet (DI)
  participant User as useUser (DI)
  participant API as ApiKeyHttpService

  UI->>Hooks: invoke query/mutation
  Hooks->>Wallet: read isManaged
  Hooks->>User: read user, isTrialing
  alt isManaged && user && !isTrialing
    Hooks->>API: get/create/delete
    API-->>Hooks: response
    Hooks-->>UI: data/result
  else not eligible
    Hooks-->>UI: undefined / no-op
  end
Loading
sequenceDiagram
  participant Handler as usePayingCustomerRequiredEventHandler
  participant Require as requireAction / Popup
  participant Router as router

  Handler->>Require: actions = ({ close }) => [...]
  Require-->>Handler: render actions
  Handler->>Router: push(payment)
  Handler->>Require: close()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • stalniy
  • devalpatel67
  • ygrishajev

Poem

I hopped through code where gates now bind,
Keys wait for wallets that are managed and kind.
A popup nudges “Add funds,” then gently close,
Tests and seeders planted in tidy rows.
I twitch my whiskers — CI, please be kind. 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/api-keys-no-wallet

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codecov
Copy link

codecov bot commented Aug 14, 2025

Codecov Report

❌ Patch coverage is 66.66667% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.44%. Comparing base (a4ba668) to head (01a852a).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../deploy-web/src/components/api-keys/ApiKeyList.tsx 0.00% 2 Missing ⚠️
...rc/hooks/usePayingCustomerRequiredEventHandler.tsx 0.00% 2 Missing ⚠️
apps/deploy-web/src/queries/useApiKeysQuery.ts 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1813      +/-   ##
==========================================
- Coverage   42.62%   42.44%   -0.18%     
==========================================
  Files         938      933       -5     
  Lines       26444    26271     -173     
  Branches     6924     6911      -13     
==========================================
- Hits        11273    11152     -121     
- Misses      13997    14757     +760     
+ Partials     1174      362     -812     
Flag Coverage Δ *Carryforward flag
api 81.21% <ø> (ø) Carriedforward from b14c640
deploy-web 20.94% <66.66%> (+0.16%) ⬆️ Carriedforward from b14c640
log-collector ?
notifications 87.59% <ø> (ø) Carriedforward from b14c640
provider-console 81.48% <ø> (ø)
provider-proxy 84.75% <ø> (ø) Carriedforward from b14c640

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
apps/deploy-web/src/queries/useApiKeysQuery.ts 91.89% <90.90%> (+77.18%) ⬆️
.../deploy-web/src/components/api-keys/ApiKeyList.tsx 0.00% <0.00%> (ø)
...rc/hooks/usePayingCustomerRequiredEventHandler.tsx 0.00% <0.00%> (ø)

... and 192 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (2)
packages/ui/context/PopupProvider/PopupProvider.tsx (1)

6-15: Strengthen CustomPopupProps typing to support functional actions

You’re supporting function-based actions at runtime, but the current type doesn’t explicitly allow it. Tighten the type to encode the functional form and avoid implicit anys.

-type ConfirmPopupProps = string | (Omit<CommonProps, "onClose" | "open"> & Omit<ConfirmProps, "onValidate" | "onCancel" | "variant">);
-type SelectPopupProps = Omit<CommonProps, "onClose" | "open"> & Omit<SelectProps, "onValidate" | "onCancel" | "variant">;
-type CustomPopupProps = Omit<CommonProps, "onClose" | "open"> & Omit<CustomPrompt, "onValidate" | "onCancel" | "variant">;
+type ConfirmPopupProps = string | (Omit<CommonProps, "onClose" | "open"> & Omit<ConfirmProps, "onValidate" | "onCancel" | "variant">);
+type SelectPopupProps = Omit<CommonProps, "onClose" | "open"> & Omit<SelectProps, "onValidate" | "onCancel" | "variant">;
+type BaseCustomPopupProps = Omit<CommonProps, "onClose" | "open"> & Omit<CustomPrompt, "onValidate" | "onCancel" | "variant" | "actions">;
+type CustomPopupProps = BaseCustomPopupProps & {
+  actions?: CustomPrompt["actions"] | ((ctx: { close: () => void }) => CustomPrompt["actions"]);
+};
 
 type PopupProviderContext = {
   confirm: (messageOrProps: ConfirmPopupProps) => Promise<boolean>;
   select: (props: SelectPopupProps) => Promise<string | undefined>;
   requireAction: (props: CustomPopupProps) => Promise<undefined>;
   createCustom: (props: CustomPopupProps) => void;
 };
apps/deploy-web/src/components/api-keys/ApiKeyList.tsx (1)

81-84: Avoid mutating props: sort a copy before mapping

Table rendering currently sorts apiKeys in place, mutating props. Clone before sort to avoid side effects.

-              {apiKeys
-                ?.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
-                ?.map(key => (
+              {(apiKeys ?? [])
+                .slice()
+                .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
+                .map(key => (
🧹 Nitpick comments (6)
apps/deploy-web/src/hooks/usePayingCustomerRequiredEventHandler.tsx (2)

19-21: Stop event propagation to avoid unintended parent handlers

You already prevent default. Add stopPropagation() to ensure no parent onClick handlers run and accidentally trigger navigation or other side effects.

-      const preventer: MouseEventHandler = event => {
-        event.preventDefault();
+      const preventer: MouseEventHandler = event => {
+        event.preventDefault();
+        event.stopPropagation();
         requireAction({

43-44: Include router in useCallback deps to avoid stale closures

router is captured in the callback; include it in the dependency array to satisfy exhaustive-deps and avoid potential staleness.

-    [isTrialing, isManaged, requireAction, user?.userId]
+    [isTrialing, isManaged, requireAction, user?.userId, router]
packages/ui/context/PopupProvider/PopupProvider.tsx (1)

87-112: Use Subject for requireAction clarity

requireAction does not return a meaningful value; a Subject communicates that intent better than Subject<SelectOption["value"] | undefined>.

-  const requireAction: PopupProviderContext["requireAction"] = useCallback(
-    (props: CustomPopupProps) => {
-      let subject: Subject<SelectOption["value"] | undefined> | undefined = new Subject<SelectOption["value"] | undefined>();
+  const requireAction: PopupProviderContext["requireAction"] = useCallback(
+    (props: CustomPopupProps) => {
+      let subject: Subject<void> | undefined = new Subject<void>();
 
       const close = () => {
         if (subject) {
-          subject.next(undefined);
+          subject.next();
           subject.complete();
           setPopupProps(undefined);
           subject = undefined;
         }
       };
 
       setPopupProps({
         title: "Action Required",
         ...props,
         actions: typeof props.actions === "function" ? props.actions({ close }) : props.actions,
         open: true,
         variant: "custom",
         onClose: close
       });
 
-      return firstValueFrom(subject).then(() => undefined);
+      return firstValueFrom(subject).then(() => undefined);
     },
     [setPopupProps]
   );
apps/deploy-web/tests/seeders/wallet.ts (1)

12-15: Preserve function signatures with typed jest mocks

Type the mocks against the WalletProviderContextType function signatures to retain type safety in tests.

-  connectManagedWallet: jest.fn(),
-  logout: jest.fn(),
-  signAndBroadcastTx: jest.fn(),
+  connectManagedWallet: jest.fn() as jest.MockedFunction<WalletProviderContextType["connectManagedWallet"]>,
+  logout: jest.fn() as jest.MockedFunction<WalletProviderContextType["logout"]>,
+  signAndBroadcastTx: jest.fn() as jest.MockedFunction<WalletProviderContextType["signAndBroadcastTx"]>,
   isManaged: true,
@@
-  switchWalletType: jest.fn(),
+  switchWalletType: jest.fn() as jest.MockedFunction<WalletProviderContextType["switchWalletType"]>,

Also applies to: 21-21

apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (2)

19-28: Test assertion should verify query is disabled, not just undefined data

The test title says "should return null" but the assertion checks for undefined. Additionally, when enabled is false in React Query, the query won't execute and data will be undefined by default. Consider also asserting that the query is not fetching to make the test more explicit.

     it("should return null when user is not provided", async () => {
       const { result } = setupApiKeysQuery({
         user: undefined,
         wallet: mockWallet
       });
 
       await waitFor(() => {
         expect(result.current.query.data).toBeUndefined();
+        expect(result.current.query.isFetching).toBe(false);
       });
     });

30-39: Consistent test assertions needed for disabled query states

Similar to the previous comment, these tests should also verify that the query is not fetching when disabled due to isTrialing or !isManaged conditions.

     it("should return null when user is trialing", async () => {
       const { result } = setupApiKeysQuery({
         user: mockUser,
         wallet: { ...mockWallet, isTrialing: true }
       });
 
       await waitFor(() => {
         expect(result.current.query.data).toBeUndefined();
+        expect(result.current.query.isFetching).toBe(false);
       });
     });
 
     it("should return null when wallet is not managed", async () => {
       const { result } = setupApiKeysQuery({
         user: mockUser,
         wallet: { ...mockWallet, isManaged: false }
       });
 
       await waitFor(() => {
         expect(result.current.query.data).toBeUndefined();
+        expect(result.current.query.isFetching).toBe(false);
       });
     });

Also applies to: 41-50

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a4ba668 and 279fae4.

📒 Files selected for processing (8)
  • apps/deploy-web/src/components/api-keys/ApiKeyList.tsx (2 hunks)
  • apps/deploy-web/src/hooks/usePayingCustomerRequiredEventHandler.tsx (1 hunks)
  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (1 hunks)
  • apps/deploy-web/src/queries/useApiKeysQuery.ts (2 hunks)
  • apps/deploy-web/tests/seeders/apiKey.ts (1 hunks)
  • apps/deploy-web/tests/seeders/index.ts (1 hunks)
  • apps/deploy-web/tests/seeders/wallet.ts (1 hunks)
  • packages/ui/context/PopupProvider/PopupProvider.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
apps/{deploy-web,provider-console}/**/*.spec.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/query-by-in-tests.mdc)

Use queryBy methods instead of getBy methods in test expectations in .spec.tsx files

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
  • apps/deploy-web/src/hooks/usePayingCustomerRequiredEventHandler.tsx
  • apps/deploy-web/tests/seeders/wallet.ts
  • apps/deploy-web/tests/seeders/apiKey.ts
  • apps/deploy-web/src/components/api-keys/ApiKeyList.tsx
  • packages/ui/context/PopupProvider/PopupProvider.tsx
  • apps/deploy-web/src/queries/useApiKeysQuery.ts
  • apps/deploy-web/tests/seeders/index.ts
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
  • apps/deploy-web/src/hooks/usePayingCustomerRequiredEventHandler.tsx
  • apps/deploy-web/tests/seeders/wallet.ts
  • apps/deploy-web/tests/seeders/apiKey.ts
  • apps/deploy-web/src/components/api-keys/ApiKeyList.tsx
  • packages/ui/context/PopupProvider/PopupProvider.tsx
  • apps/deploy-web/src/queries/useApiKeysQuery.ts
  • apps/deploy-web/tests/seeders/index.ts
🧠 Learnings (1)
📚 Learning: 2025-07-21T08:24:27.953Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/query-by-in-tests.mdc:0-0
Timestamp: 2025-07-21T08:24:27.953Z
Learning: Applies to apps/{deploy-web,provider-console}/**/*.spec.tsx : Use `queryBy` methods instead of `getBy` methods in test expectations in `.spec.tsx` files

Applied to files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
🧬 Code Graph Analysis (3)
apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (6)
apps/deploy-web/tests/seeders/apiKey.ts (1)
  • buildApiKey (4-13)
apps/deploy-web/src/types/user.ts (1)
  • CustomUserProfile (19-19)
apps/deploy-web/tests/seeders/user.ts (1)
  • buildUser (7-25)
apps/deploy-web/tests/seeders/wallet.ts (1)
  • buildWallet (7-25)
packages/http-sdk/src/api-key/api-key-http.service.ts (1)
  • ApiKeyHttpService (4-16)
apps/deploy-web/src/queries/useApiKeysQuery.ts (4)
  • USE_API_KEYS_DEPENDENCIES (10-13)
  • useCreateApiKey (34-53)
  • useDeleteApiKey (55-69)
  • useUserApiKeys (15-32)
apps/deploy-web/src/hooks/usePayingCustomerRequiredEventHandler.tsx (1)
apps/deploy-web/src/utils/urlUtils.ts (1)
  • UrlService (16-81)
apps/deploy-web/src/queries/useApiKeysQuery.ts (3)
apps/api/src/auth/http-schemas/api-key.schema.ts (1)
  • ApiKeyResponse (58-58)
packages/http-sdk/src/api-key/api-key-http.types.ts (1)
  • ApiKeyResponse (32-32)
apps/deploy-web/src/queries/queryKeys.ts (1)
  • QueryKeys (1-107)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: Validate local packages
🔇 Additional comments (11)
apps/deploy-web/src/hooks/usePayingCustomerRequiredEventHandler.tsx (2)

27-35: Good use of close callback in popup actions

Switching actions to a function and invoking close() after navigation is correct and aligns with the updated Popup API.


41-41: Confirm gating logic matches product intent

We’ve verified that across the codebase all “paying customer” gates use the exact same condition:

user?.userId && isManaged && !isTrialing

Key instances include:

  • usePayingCustomerRequiredEventHandler (this hook)
  • useApiKeysQuery.ts (enabled: !!user?.userId && !isTrialing && isManaged)
  • useVerifiedPayingCustomerLoginRequiredEventHandler (composes the above)

That means custodial (non-managed) wallets are currently blocked even if they hold on-chain funds.
Please confirm whether we should:

  • Continue blocking custodial wallets entirely, or
  • Broaden this condition (e.g. allow non-managed wallets once a deposit/payment is verified)
packages/ui/context/PopupProvider/PopupProvider.tsx (1)

140-146: Explicit return type for usePopup is a solid improvement

Typing usePopup to return PopupProviderContext improves DX and prevents accidental misuse.

apps/deploy-web/src/components/api-keys/ApiKeyList.tsx (1)

81-112: Render API key rows or the empty state — never both

Extract and sort your keys once, then decide up front whether to show rows or the “No API keys found” message:

@@ apps/deploy-web/src/components/api-keys/ApiKeyList.tsx
-  const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
+  const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);

+  // Sort once for readability and performance
+  const sortedApiKeys = (apiKeys ?? [])
+    .slice()
+    .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());

+  // Decide whether to render rows or the empty-state
+  const shouldShowEmptyState =
+    sortedApiKeys.length === 0 || isTrialing || !isManaged;
@@
-              {apiKeys
-                ?.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
-                ?.map(key => (
+              {!shouldShowEmptyState
+                ? sortedApiKeys.map(key => (
                   <TableRow key={key.id}>
                     <TableCell>{key.name}</TableCell>
                     {/* … */}
                   </TableRow>
                 ))
+                : (
+                  <TableRow>
+                    <TableCell colSpan={5} className="text-center">
+                      No API keys found
+                    </TableCell>
+                  </TableRow>
+                )}

• Sorting in render can be costly; hoisting it improves clarity and performance.
• Using a single boolean makes the JSX much easier to follow.
Please confirm: Should trialing or non-managed wallets always show the empty state (hiding any existing keys), or only when there truly are zero keys?

apps/deploy-web/tests/seeders/index.ts (1)

1-14: Barrel file looks good

Clean aggregator for test seeders; reduces import noise and matches added modules.

apps/deploy-web/tests/seeders/apiKey.ts (2)

1-13: LGTM!

The seeder implementation is well-structured with proper faker usage for generating realistic test data. The buildApiKey function correctly spreads overrides last to allow customization of generated values.


15-16: LGTM!

The buildApiKeys helper correctly creates multiple API keys with the same overrides pattern, using a functional approach with Array.from.

apps/deploy-web/src/queries/useApiKeysQuery.ts (4)

10-13: LGTM! Clean dependency injection pattern

The introduction of USE_API_KEYS_DEPENDENCIES provides a clean separation of concerns and enables better testability by allowing dependency injection.


19-26: LGTM! Proper implementation of isManaged gating

The implementation correctly gates API key operations behind the isManaged flag along with existing conditions, and properly uses dependency injection.


34-52: LGTM! Clean dependency injection for create mutation

The useCreateApiKey hook properly uses dependency injection and correctly updates the query cache on success.


55-68: LGTM! Well-structured delete mutation with callback support

The useDeleteApiKey hook properly implements dependency injection, updates the cache correctly by filtering out the deleted key, and supports an optional success callback.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
apps/deploy-web/src/queries/useApiKeysQuery.ts (1)

34-53: Guard useCreateApiKey against missing user, trial, and unmanaged wallets

Even though the “Create Key” button is wrapped in a paying-customer guard, it’s safer to enforce this at the hook level so that any future callers won’t accidentally write under an empty user key or invoke the API in unsupported wallet states.

• apps/deploy-web/src/queries/useApiKeysQuery.ts (lines 34–53):
– Destructure isTrialing and isManaged from dependencies.useWallet()
– In mutationFn, reject if !user?.userId, isTrialing, or !isManaged
– In onSuccess, skip cache updates when user?.userId is falsy

 export function useCreateApiKey(
-  dependencies: typeof USE_API_KEYS_DEPENDENCIES = USE_API_KEYS_DEPENDENCIES
+  dependencies: typeof USE_API_KEYS_DEPENDENCIES = USE_API_KEYS_DEPENDENCIES
 ) {
   const user = dependencies.useUser();
+  const { isTrialing, isManaged } = dependencies.useWallet();
   const queryClient = useQueryClient();
   const { apiKey } = useServices();

   return useMutation<ApiKeyResponse, Error, string>({
-    mutationFn: (name: string) =>
-      apiKey.createApiKey({ data: { name } }),
+    mutationFn: (name: string) => {
+      if (!user?.userId) {
+        return Promise.reject(new Error("Missing user ID"));
+      }
+      if (isTrialing) {
+        return Promise.reject(new Error("Cannot create API key during trial"));
+      }
+      if (!isManaged) {
+        return Promise.reject(new Error("Cannot create API key with unmanaged wallet"));
+      }
+      return apiKey.createApiKey({ data: { name } });
+    },
     onSuccess: _response => {
-      queryClient.setQueryData(QueryKeys.getApiKeysKey(user?.userId ?? ""), (oldData) => {
-        if (!oldData) return [_response];
+      if (!user?.userId) return;
+      queryClient.setQueryData(QueryKeys.getApiKeysKey(user.userId), (oldData) => {
+        if (!oldData) return [_response];
         return [...oldData, _response];
       });
     }
   });
 }
🧹 Nitpick comments (5)
apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (5)

31-40: Align description and harden the trialing test

The title says “null” but the assertion checks undefined. Also assert the query is disabled and the service is not called when isTrialing is true.

Apply this diff:

-    it("should return null when user is trialing", async () => {
-      const { result } = setupApiKeysQuery({
-        user: mockUser,
-        wallet: { ...mockWallet, isTrialing: true }
-      });
-
-      await waitFor(() => {
-        expect(result.current.query.data).toBeUndefined();
-      });
-    });
+    it("should return undefined and not fetch when user is trialing", async () => {
+      const apiKeyService = mock<ApiKeyHttpService>({
+        getApiKeys: jest.fn()
+      });
+      const { result } = setupApiKeysQuery({
+        user: mockUser,
+        wallet: { ...mockWallet, isTrialing: true },
+        services: {
+          apiKey: () => apiKeyService
+        }
+      });
+
+      expect(result.current.query.fetchStatus).toBe("idle");
+      expect(apiKeyService.getApiKeys).not.toHaveBeenCalled();
+      expect(result.current.query.data).toBeUndefined();
+    });

42-51: Harden the “wallet not managed” test

Mirror the pattern above: assert disabled state and that the service is not invoked.

Apply this diff:

-    it("should return null when wallet is not managed", async () => {
-      const { result } = setupApiKeysQuery({
-        user: mockUser,
-        wallet: { ...mockWallet, isManaged: false }
-      });
-
-      await waitFor(() => {
-        expect(result.current.query.data).toBeUndefined();
-      });
-    });
+    it("should return undefined and not fetch when wallet is not managed", async () => {
+      const apiKeyService = mock<ApiKeyHttpService>({
+        getApiKeys: jest.fn()
+      });
+      const { result } = setupApiKeysQuery({
+        user: mockUser,
+        wallet: { ...mockWallet, isManaged: false },
+        services: {
+          apiKey: () => apiKeyService
+        }
+      });
+
+      expect(result.current.query.fetchStatus).toBe("idle");
+      expect(apiKeyService.getApiKeys).not.toHaveBeenCalled();
+      expect(result.current.query.data).toBeUndefined();
+    });

103-138: Also assert cache is updated by create mutation

The hook updates the query cache on success. Strengthen the test by asserting the cache now holds the created key at the expected key.

Apply this diff:

-      const { result } = setupQuery(
+      const { result, queryClient } = setupQuery(
         () => {
           const dependencies: typeof USE_API_KEYS_DEPENDENCIES = {
             ...USE_API_KEYS_DEPENDENCIES,
             useUser: () => mockUser,
             useWallet: () => mockWallet
           };
           return useCreateApiKey(dependencies);
         },
         {
           services: {
             apiKey: () => apiKeyService
           }
         }
       );
@@
       await waitFor(() => {
         expect(result.current.isSuccess).toBe(true);
       });
 
       expect(apiKeyService.createApiKey).toHaveBeenCalledWith({
         data: { name: "New Key" }
       });
+
+      const cached = queryClient.getQueryData<ApiKeyResponse[]>(["API_KEYS", mockUser.userId]);
+      expect(cached).toEqual([newApiKey]);

140-171: Pre-seed and assert deletion updates the cache

The delete hook filters the cached list on success. Seed the cache with the key under test and assert it’s removed afterwards.

Apply this diff:

-      const { result } = setupQuery(
+      const { result, queryClient } = setupQuery(
         () => {
           const dependencies: typeof USE_API_KEYS_DEPENDENCIES = {
             ...USE_API_KEYS_DEPENDENCIES,
             useUser: () => mockUser,
             useWallet: () => mockWallet
           };
           return useDeleteApiKey("key-1", undefined, dependencies);
         },
         {
           services: {
             apiKey: () => apiKeyService
           }
         }
       );
 
-      act(() => {
+      // Seed cache with two keys, one will be deleted
+      const existing = [buildApiKey({ id: "key-1" }), buildApiKey({ id: "key-2" })];
+      act(() => {
+        queryClient.setQueryData(["API_KEYS", mockUser.userId], existing);
         result.current.mutate();
       });
@@
       expect(apiKeyService.deleteApiKey).toHaveBeenCalledWith("key-1");
+
+      const cached = queryClient.getQueryData<ApiKeyResponse[]>(["API_KEYS", mockUser.userId]);
+      expect(cached?.map(k => k.id)).toEqual(["key-2"]);

214-221: Optional: Avoid exposing queryClient via result.current

You’re pulling queryClient via useQueryClient() and returning it through result.current. If setupQuery already returns queryClient, prefer destructuring it from the setup return value to reduce coupling to the hook’s return shape.

Apply this diff to simplify:

-      () => {
-        const queryClient = useQueryClient();
-        return {
-          query: useUserApiKeys({}, dependencies),
-          dependencies,
-          queryClient
-        };
-      },
+      () => useUserApiKeys({}, dependencies),

Then in the tests that need it:

-      const { result } = setupApiKeysQuery({ /* ... */ });
+      const { result, queryClient } = setupApiKeysQuery({ /* ... */ });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 279fae4 and 6b7a1ef.

📒 Files selected for processing (2)
  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (1 hunks)
  • apps/deploy-web/src/queries/useApiKeysQuery.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
apps/{deploy-web,provider-console}/**/*.spec.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/query-by-in-tests.mdc)

Use queryBy methods instead of getBy methods in test expectations in .spec.tsx files

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
  • apps/deploy-web/src/queries/useApiKeysQuery.ts
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
  • apps/deploy-web/src/queries/useApiKeysQuery.ts
🧠 Learnings (4)
📚 Learning: 2025-07-21T08:24:27.953Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/query-by-in-tests.mdc:0-0
Timestamp: 2025-07-21T08:24:27.953Z
Learning: Applies to apps/{deploy-web,provider-console}/**/*.spec.tsx : Use `queryBy` methods instead of `getBy` methods in test expectations in `.spec.tsx` files

Applied to files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : `setup` function must be at the bottom of the root `describe` block in test files

Applied to files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : Use `setup` function instead of `beforeEach` in test files

Applied to files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
📚 Learning: 2025-07-27T12:16:08.566Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-07-27T12:16:08.566Z
Learning: Applies to **/*.{ts,tsx} : Never use type any or cast to type any. Always define the proper TypeScript types.

Applied to files:

  • apps/deploy-web/src/queries/useApiKeysQuery.ts
🧬 Code Graph Analysis (2)
apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (6)
apps/deploy-web/tests/seeders/apiKey.ts (1)
  • buildApiKey (4-13)
apps/deploy-web/src/types/user.ts (1)
  • CustomUserProfile (19-19)
apps/deploy-web/tests/seeders/user.ts (1)
  • buildUser (7-25)
apps/deploy-web/tests/seeders/wallet.ts (1)
  • buildWallet (7-25)
packages/http-sdk/src/api-key/api-key-http.service.ts (1)
  • ApiKeyHttpService (4-16)
apps/deploy-web/src/queries/useApiKeysQuery.ts (4)
  • USE_API_KEYS_DEPENDENCIES (10-13)
  • useCreateApiKey (34-53)
  • useDeleteApiKey (55-69)
  • useUserApiKeys (15-32)
apps/deploy-web/src/queries/useApiKeysQuery.ts (3)
apps/api/src/auth/http-schemas/api-key.schema.ts (1)
  • ApiKeyResponse (58-58)
packages/http-sdk/src/api-key/api-key-http.types.ts (1)
  • ApiKeyResponse (32-32)
apps/deploy-web/src/context/ServicesProvider/ServicesProvider.tsx (1)
  • useServices (27-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: validate (apps/deploy-web) / validate-unsafe
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: Validate local packages
🔇 Additional comments (6)
apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (3)

53-72: LGTM: Positive-path fetch is correctly asserted

Mocks, DI wiring, and expectations look correct, and the test validates both service invocation and returned data.


74-100: LGTM: Correctly validates the query key in the cache

This addresses the earlier review note about actually verifying the cache key. Nice job asserting a single match with the expected key.


173-204: LGTM: onSuccess callback is properly invoked after deletion

Clear assertion of the callback behavior with DI and mutation.

apps/deploy-web/src/queries/useApiKeysQuery.ts (3)

10-13: LGTM: DI boundary extracted for hooks

Exported USE_API_KEYS_DEPENDENCIES cleanly enables dependency injection in tests and consumers.


15-31: Good: typed UseQueryOptions and managed-wallet gating added

  • Replaced any with ApiKeyResponse[] in UseQueryOptions generics.
  • enabled now also requires isManaged and not trialing. Matches product intent.

55-69: No guard needed for useDeleteApiKey – call sites already ensure a valid user and key
All instances of useDeleteApiKey are invoked only after both the user and the API‐key ID are defined (the hook re-runs with the actual ID on state update, and the pages themselves require a logged-in user). The mutation’s onSuccess will therefore never run against the ["API_KEYS", ""] cache. No additional guard or early return is required here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (1)

257-263: Remove unsafe type assertion in setup (return possibly undefined user without casting).

Casting input?.user to CustomUserProfile is unsafe and unnecessary. The dependency contract supports an undefined user, and your gating tests rely on that. Returning the value as-is improves type safety and aligns with the DI behavior in production.

Apply this diff:

-    const dependencies: typeof USE_API_KEYS_DEPENDENCIES = {
+    const dependencies: typeof USE_API_KEYS_DEPENDENCIES = {
       ...USE_API_KEYS_DEPENDENCIES,
-      useUser: () => input?.user as CustomUserProfile,
+      useUser: () => input?.user,
       useWallet: () => input?.wallet || mockWallet
     };
🧹 Nitpick comments (1)
apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (1)

34-43: Strengthen disabled-state assertion (explicitly verify fetchStatus is "idle").

You already assert the service wasn’t invoked and the query didn’t transition to loading/success. Add an explicit check for the disabled fetch status for completeness and consistency with the other gating tests.

Apply this diff:

       // Verify the service was not called since the query is disabled
       expect(apiKeyService.getApiKeys).not.toHaveBeenCalled();

+      // Disabled queries remain idle
+      expect(result.current.query.fetchStatus).toBe("idle");
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b14c640 and 01a852a.

📒 Files selected for processing (1)
  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
apps/{deploy-web,provider-console}/**/*.spec.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/query-by-in-tests.mdc)

Use queryBy methods instead of getBy methods in test expectations in .spec.tsx files

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
🧠 Learnings (4)
📚 Learning: 2025-07-21T08:24:27.953Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/query-by-in-tests.mdc:0-0
Timestamp: 2025-07-21T08:24:27.953Z
Learning: Applies to apps/{deploy-web,provider-console}/**/*.spec.tsx : Use `queryBy` methods instead of `getBy` methods in test expectations in `.spec.tsx` files

Applied to files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
📚 Learning: 2025-07-21T08:24:24.269Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-07-21T08:24:24.269Z
Learning: Applies to **/*.spec.{ts,tsx} : Don't use `jest.mock()` to mock dependencies in test files. Instead, use `jest-mock-extended` to create mocks and pass mocks as dependencies to the service under test.

Applied to files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : `setup` function must be at the bottom of the root `describe` block in test files

Applied to files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : Use `setup` function instead of `beforeEach` in test files

Applied to files:

  • apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx
🧬 Code Graph Analysis (1)
apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (6)
apps/deploy-web/tests/seeders/apiKey.ts (1)
  • buildApiKey (4-13)
apps/deploy-web/src/types/user.ts (1)
  • CustomUserProfile (19-19)
apps/deploy-web/tests/seeders/user.ts (1)
  • buildUser (7-25)
apps/deploy-web/tests/seeders/wallet.ts (1)
  • buildWallet (7-25)
packages/http-sdk/src/api-key/api-key-http.service.ts (1)
  • ApiKeyHttpService (4-16)
apps/deploy-web/src/queries/useApiKeysQuery.ts (4)
  • USE_API_KEYS_DEPENDENCIES (10-13)
  • useCreateApiKey (34-53)
  • useDeleteApiKey (55-69)
  • useUserApiKeys (15-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: Validate local packages
🔇 Additional comments (4)
apps/deploy-web/src/queries/useApiKeysQuery.spec.tsx (4)

45-60: LGTM: Gating correctly disables fetching for trialing and non-managed wallets.

Both tests clearly assert idle fetch status, no service invocation, and undefined data. This validates the enabled logic against isTrialing and isManaged.

Also applies to: 62-77


79-98: LGTM: Happy path fetch asserts success and data shape.

Verifies the service is called and the returned data is surfaced correctly.


100-128: Resolved: Test now verifies the query key via the QueryClient cache.

This addresses prior feedback by asserting the exact query key used to cache the result. The approach of injecting a dedicated QueryClient is solid.


131-172: LGTM: Mutations update the cache correctly (create, delete) and invoke callbacks.

  • Create: onSuccess appends to cache; assertion checks the new key present.
  • Delete: pre-seeded cache removes target entry; onSuccess callback validated.

Well-isolated dependencies via DI and per-test QueryClient instances keep tests hermetic.

Also applies to: 175-221, 223-254

@baktun14 baktun14 merged commit ebfcbbe into main Aug 14, 2025
63 checks passed
@baktun14 baktun14 deleted the fix/api-keys-no-wallet branch August 14, 2025 19:11
stalniy pushed a commit that referenced this pull request Nov 20, 2025
* feat(auth): enhance API key management with new hooks and tests

* fix(auth): enhance query caching and improve type safety in useUserApiKeys

* fix(auth): update useUserApiKeys tests to verify disabled state when user is not provided

* fix(auth): update useApiKeysQuery tests
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.

2 participants

Comments