Skip to content

feat: topup wallet returns updated wallet data - #76

Merged
indrasaputra merged 4 commits into
mainfrom
e2e
Nov 1, 2025
Merged

feat: topup wallet returns updated wallet data#76
indrasaputra merged 4 commits into
mainfrom
e2e

Conversation

@indrasaputra

@indrasaputra indrasaputra commented Nov 1, 2025

Copy link
Copy Markdown
Owner

Summary

topup wallet returns updated wallet data

Description

  • topup wallet returns updated wallet data
  • fix e2e

Summary by Sourcery

Return updated wallet data in the topup flow by extending the repository, service, handler, proto definitions, SQL queries, and tests, and fix related integration tests for auth flows.

New Features:

  • Include Data field in TopupWalletResponse to return updated wallet info

Enhancements:

  • Change AddWalletBalance to return updated Wallet entity and propagate it through service and handler layers
  • Update WalletTopup and WalletTransferer interfaces and implementations to return Wallet instead of nil
  • Modify SQL AddWalletBalance query to RETURNING all wallet fields
  • Refresh swagger docs to reflect new response field and updated operation summaries

Documentation:

  • Update OpenAPI (Swagger) summaries and add Data property in TopupWalletResponse definition

Tests:

  • Revise unit tests and mocks for wallet topup, transfer, and gRPC handlers to handle returned Wallet
  • Fix integration tests for auth login and register to use dynamic account creation

Summary by CodeRabbit

Release Notes

  • New Features

    • Top-up wallet endpoint now returns updated wallet information in the response, providing immediate feedback on balance changes.
  • Documentation

    • Improved API documentation with consistent formatting and capitalization across operation descriptions.

@sourcery-ai

sourcery-ai Bot commented Nov 1, 2025

Copy link
Copy Markdown

Reviewer's Guide

This PR extends the TopupWallet flow to return the updated wallet object by adding a Data field in the response proto, updating service and repository methods to return the new wallet, adjusting SQL queries to RETURNING, and updating handlers and tests across wallet, auth, and user modules to reflect this change and fix end-to-end tests.

Sequence diagram for Topup Wallet flow returning updated wallet data

sequenceDiagram
  participant Client
  participant WalletCommandHandler
  participant WalletTopupService
  participant WalletRepository
  participant DB

  Client->>WalletCommandHandler: TopupWallet(request)
  WalletCommandHandler->>WalletTopupService: Topup(req)
  WalletTopupService->>WalletRepository: AddWalletBalance(walletID, amount)
  WalletRepository->>DB: UPDATE wallets SET balance = balance + amount RETURNING *
  DB-->>WalletRepository: Updated wallet row
  WalletRepository-->>WalletTopupService: Updated Wallet entity
  WalletTopupService-->>WalletCommandHandler: Updated Wallet entity
  WalletCommandHandler-->>Client: TopupWalletResponse{Data: updated wallet}
Loading

ER diagram for updated TopupWalletResponse and Wallet entity

erDiagram
  TOPUP_WALLET_RESPONSE {
    Wallet data
  }
  WALLET {
    id UUID
    user_id UUID
    balance DECIMAL
    created_at TIMESTAMP
    updated_at TIMESTAMP
    deleted_at TIMESTAMP
    created_by UUID
    updated_by UUID
    deleted_by UUID
  }
  TOPUP_WALLET_RESPONSE ||--|| WALLET : contains
Loading

Class diagram for updated TopupWalletResponse and related service interfaces

classDiagram
  class TopupWalletResponse {
    +Wallet Data
    +GetData()
  }

  class WalletCommand {
    +TopupWallet(ctx, request) : TopupWalletResponse
  }

  class TopupWallet {
    +Topup(ctx, topup) : Wallet
  }

  class TopupWalletRepository {
    +AddWalletBalance(ctx, id, amount) : Wallet
  }

  TopupWalletResponse --> Wallet
  WalletCommand --> TopupWallet
  TopupWallet --> TopupWalletRepository
Loading

File-Level Changes

Change Details Files
Extend TopupWalletResponse and API definitions to include updated wallet data
  • Add Data field and getter to TopupWalletResponse in protobuf
  • Update swagger spec to document data property in response
  • Refresh gRPC comments/summaries across wallet, user, auth, transaction services
proto/api/v1/wallet.pb.go
openapiv2/arjuna.swagger.yaml
proto/api/v1/wallet_grpc.pb.go
proto/api/v1/user_grpc.pb.go
proto/api/v1/auth_grpc.pb.go
proto/api/v1/transaction_grpc.pb.go
Modify repository AddWalletBalance to return the new wallet record
  • Change SQL to RETURNING full row
  • Update db query and scan into Wallet struct
  • Adjust method signature to return *Wallet and error
service/wallet/internal/repository/db/queries.sql.go
service/wallet/db/queries/queries.sql
service/wallet/internal/repository/postgres/wallet.go
service/wallet/internal/repository/postgres/wallet_test.go
Update service layer to propagate returned wallet object
  • Change Topup and TransferBalance interfaces to return *Wallet
  • Adjust implementation to return wallet on success or nil on error
  • Update mocks to match new signatures
service/wallet/internal/service/wallet_topup.go
service/wallet/internal/service/wallet_transferer.go
service/wallet/test/mock/service/wallet_topup.go
service/wallet/test/mock/service/wallet_transferer.go
Adjust gRPC handler to return wallet data in response
  • Capture returned entity.Wallet and map to proto
  • Return TopupWalletResponse with Data populated
  • Update handler tests to expect Data in success scenario
service/wallet/internal/grpc/handler/wallet_command.go
service/wallet/internal/grpc/handler/wallet_command_test.go
Revise unit tests in wallet_topup service to assert returned wallet
  • Capture wallet return in test calls
  • Assert wallet is nil on error and non-nil on success
service/wallet/internal/service/wallet_topup_test.go
Fix auth integration tests to register account before login
  • Add account registration step in login tests
  • Use dynamic account.Email and account.Password in payloads
  • Adjust test order for valid/invalid scenarios
service/auth/test/integration/login_test.go
service/auth/test/integration/register_test.go
service/auth/test/integration/helper_test.go
Update user integration tests for dynamic data
  • Generate unique email in helper and register tests
  • Remove fixed credentials globals
service/user/test/integration/register_test.go
service/user/test/integration/helper_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Nov 1, 2025

Copy link
Copy Markdown

Walkthrough

Documentation comments across proto files are updated for consistency. Auth and user service tests are refactored to generate unique test credentials dynamically. The wallet service is extended: TopupWalletResponse now includes wallet data, and AddWalletBalance/Topup methods return the updated wallet object instead of only errors, requiring corresponding changes to database queries, service layers, and all related tests and mocks.

Changes

Cohort / File(s) Summary
Proto documentation updates
service/auth/api/v1/auth.proto, service/transaction/api/v1/transaction.proto, service/user/api/v1/user.proto, service/wallet/api/v1/wallet.proto, openapiv2/arjuna.swagger.yaml
Comments adjusted for consistency: trailing periods removed, spacing normalized (e.g., "Create Transaction", "Register Account"). OpenAPI summaries updated similarly.
Wallet proto schema
service/wallet/api/v1/wallet.proto
TopupWalletResponse message now includes new data field of type Wallet with OUTPUT_ONLY behavior.
Auth test refactoring
service/auth/test/integration/helper_test.go, service/auth/test/integration/login_test.go, service/auth/test/integration/register_test.go
Helper removes global email/password fixtures; tests now dynamically generate unique credentials per test (login registers account before testing; register uses uuid-based email format).
User test refactoring
service/user/test/integration/helper_test.go, service/user/test/integration/register_test.go
Helper removes global email/password/name fixtures; register test populates user fields locally.
Wallet database layer
service/wallet/db/queries/queries.sql, service/wallet/internal/repository/db/queries.sql.go, service/wallet/internal/repository/postgres/wallet.go, service/wallet/internal/repository/postgres/wallet_test.go
AddWalletBalance query and handler signature changed from :exec (return error) to :one (return \*Wallet, error) with RETURNING clause; tests updated to mock row returns and validate wallet fields.
Wallet service layer
service/wallet/internal/service/wallet_topup.go, service/wallet/internal/service/wallet_topup_test.go, service/wallet/internal/service/wallet_transferer.go, service/wallet/internal/service/wallet_transferer_test.go
Topup and AddWalletBalance interfaces/implementations return (\*entity.Wallet, error) instead of error; all error paths updated to return (nil, error); success paths return (wallet, nil); tests refactored to assert wallet values.
Wallet gRPC handler
service/wallet/internal/grpc/handler/wallet_command.go, service/wallet/internal/grpc/handler/wallet_command_test.go
TopupWallet now returns wallet data via new createWalletProto helper; mock updated to return wallet; test scaffolding adjusted for wallet object validation.
Wallet test mocks
service/wallet/test/mock/service/wallet_topup.go, service/wallet/test/mock/service/wallet_transferer.go
Mock methods AddWalletBalance and Topup updated to return (\*entity.Wallet, error); recorder signatures and return value extraction adjusted.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Handler as gRPC Handler
    participant Service as Topup Service
    participant Repo as Repository
    participant DB as Database

    rect rgb(200, 220, 240)
    Note over Client,DB: Before: TopupWallet returned only error
    Client->>Handler: TopupWallet()
    Handler->>Service: Topup()
    Service->>Repo: AddWalletBalance()
    Repo->>DB: UPDATE + balance
    DB-->>Repo: error
    Repo-->>Service: error
    Service-->>Handler: error
    Handler-->>Client: empty TopupWalletResponse{}
    end

    rect rgb(220, 240, 200)
    Note over Client,DB: After: TopupWallet returns wallet data
    Client->>Handler: TopupWallet()
    Handler->>Service: Topup()
    Service->>Repo: AddWalletBalance()
    Repo->>DB: UPDATE ... RETURNING *
    DB-->>Repo: Wallet {ID, UserID, Balance}
    Repo-->>Service: (*Wallet, error)
    Service-->>Handler: (*Wallet, error)
    Handler->>Handler: createWalletProto(wallet)
    Handler-->>Client: TopupWalletResponse{Data: Wallet}
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Primary concerns:
    • Wallet service signature changes propagate across repository, service, handler, and test layers; verify all implementations and callers are consistent.
    • Database query modifications (:exec:one with RETURNING clause) require careful review of SQL correctness and generated code mapping.
    • Auth and user test credential generation logic (uuid-based emails, dynamic password assignment) should be verified for uniqueness and test isolation.
    • Mock return value updates across multiple files; ensure type casting and error propagation are correct.
    • Proto schema changes (TopupWalletResponse) and OpenAPI updates should be cross-validated.

Possibly related PRs

Suggested labels

enhancement, tests, Review effort [1-5]: 4

Poem

🐰 Hops through the wallet, now with grace,
Credentials unique in every test-case,
TopupWallet returns the data it earned,
No more empty responses—balances returned!
From proto to queries, the flow dances bright,

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: topup wallet returns updated wallet data" directly and clearly describes the primary change in this pull request. The raw summary confirms that the main objective is to modify the TopupWallet operation to return updated wallet data through changes across proto definitions, SQL queries, repository, service, and handler layers. The title is specific, concise, and accurately represents the feature being implemented without being misleading or overly vague.
Description Check ✅ Passed The PR description follows the required template structure with both the "Summary" and "Description" sections present and populated with content. While the author-written summary and description bullets are minimal (with "fix e2e" being somewhat vague), the template itself is basic and both required sections have substantive content. Additionally, the Sourcery AI summary provides comprehensive categorized details about the changes across new features, enhancements, documentation, and tests, giving reviewers sufficient context to understand the scope of modifications.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch e2e

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

level=warning msg="[linters_context] running gomodguard failed: unable to read module file go.mod: current working directory must have a go.mod file: if you are not using go modules it is suggested to disable this linter"
level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


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.

@qodo-code-review

qodo-code-review Bot commented Nov 1, 2025

Copy link
Copy Markdown

PR Compliance Guide 🔍

(Compliance updated until commit 531dfe9)

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logs: The new topup flow returns updated wallet data but does not add audit logging of the
critical balance change event with user, wallet, amount, and outcome context.

Referred Code
	amount, _ := decimal.NewFromString(request.GetTopup().GetAmount())
	req := createTopupWalletFromTopupWalletRequest(request, userID, amount, key[0])

	wallet, err := wc.topup.Topup(ctx, req)
	if err != nil {
		slog.ErrorContext(ctx, "[WalletCommand-TopupWallet] fail topup wallet", "error", err)
		return nil, err
	}
	return &apiv1.TopupWalletResponse{Data: createWalletProto(wallet)}, nil
}
Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Error detail propagation: Internal repository wraps and returns the original error message via
entity.ErrInternal(err.Error()), which might expose backend details to user-facing layers
if not sanitized upstream.

Referred Code
}

// AddWalletBalance adds some amount to specific user's wallet.
func (w *Wallet) AddWalletBalance(ctx context.Context, id uuid.UUID, amount decimal.Decimal) (*entity.Wallet, error) {
	param := db.AddWalletBalanceParams{ID: id, Amount: amount}
	res, err := w.queries.AddWalletBalance(ctx, param)
	if err != nil {
		slog.ErrorContext(ctx, "[WalletPostgres-addWalletBalance] internal error", "error", err)
		return nil, entity.ErrInternal(err.Error())
	}
	return &entity.Wallet{
		ID:      res.ID,
		UserID:  res.UserID,
		Balance: res.Balance,
	}, nil
}
Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Potential PII logging: Logs include idempotency_key and may include wallet identifiers alongside errors, which
could be sensitive and require confirmation that logging is compliant with policies.

Referred Code
// Topup topups wallet's balance.
// It needs idempotency key.
func (wt *WalletTopup) Topup(ctx context.Context, topup *entity.TopupWallet) (*entity.Wallet, error) {
	if topup == nil {
		return nil, entity.ErrEmptyWallet()
	}

	if err := wt.validateIdempotencyKey(ctx, topup.IdempotencyKey); err != nil {
		slog.ErrorContext(ctx, "[WalletTopup-Topup] fail check idempotency key", "idempotency_key", topup.IdempotencyKey, "error", err)
		return nil, err
	}

	if err := validateTopupWallet(topup); err != nil {
		slog.ErrorContext(ctx, "[WalletTopup-Topup] wallet is invalid", "error", err)
		return nil, err
	}

	wallet, err := wt.walletRepo.AddWalletBalance(ctx, topup.WalletID, topup.Amount)
	if err != nil {
		slog.ErrorContext(ctx, "[WalletTopup-Topup] fail update wallet balance", "error", err)


 ... (clipped 4 lines)
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Previous compliance checks

Compliance check up to commit 531dfe9
Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Action Logging: New critical actions updating wallet balances and returning wallet data do not introduce
corresponding audit logs identifying user, action, and outcome beyond error logs, and it
is unclear if success events are logged elsewhere.

Referred Code
func (wt *WalletTopup) Topup(ctx context.Context, topup *entity.TopupWallet) (*entity.Wallet, error) {
	if topup == nil {
		return nil, entity.ErrEmptyWallet()
	}

	if err := wt.validateIdempotencyKey(ctx, topup.IdempotencyKey); err != nil {
		slog.ErrorContext(ctx, "[WalletTopup-Topup] fail check idempotency key", "idempotency_key", topup.IdempotencyKey, "error", err)
		return nil, err
	}

	if err := validateTopupWallet(topup); err != nil {
		slog.ErrorContext(ctx, "[WalletTopup-Topup] wallet is invalid", "error", err)
		return nil, err
	}

	wallet, err := wt.walletRepo.AddWalletBalance(ctx, topup.WalletID, topup.Amount)
	if err != nil {
		slog.ErrorContext(ctx, "[WalletTopup-Topup] fail update wallet balance", "error", err)
		return nil, err
	}
	return wallet, nil


 ... (clipped 1 lines)
Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Error Exposure Risk: Errors are logged with internal context and then returned upstream; without visibility
into the error-to-response mapping, there is a risk internal details could be exposed to
clients.

Referred Code
if err := wt.validateIdempotencyKey(ctx, topup.IdempotencyKey); err != nil {
	slog.ErrorContext(ctx, "[WalletTopup-Topup] fail check idempotency key", "idempotency_key", topup.IdempotencyKey, "error", err)
	return nil, err
}

if err := validateTopupWallet(topup); err != nil {
	slog.ErrorContext(ctx, "[WalletTopup-Topup] wallet is invalid", "error", err)
	return nil, err
}

wallet, err := wt.walletRepo.AddWalletBalance(ctx, topup.WalletID, topup.Amount)
if err != nil {
	slog.ErrorContext(ctx, "[WalletTopup-Topup] fail update wallet balance", "error", err)
	return nil, err
}

@sourcery-ai sourcery-ai 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.

Hey there - I've reviewed your changes - here's some feedback:

  • Consider extending the updated wallet-return pattern to the TransferBalance API for consistent behavior across wallet operations.
  • Ensure that adding the data field to TopupWalletResponse in the proto and Swagger spec does not break backward compatibility for existing clients.
  • Verify that the SQL RETURNING clause and accompanying --noqa comment in AddWalletBalance are correctly handled by your SQL codegen and do not introduce runtime errors.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider extending the updated wallet-return pattern to the TransferBalance API for consistent behavior across wallet operations.
- Ensure that adding the `data` field to TopupWalletResponse in the proto and Swagger spec does not break backward compatibility for existing clients.
- Verify that the SQL `RETURNING` clause and accompanying `--noqa` comment in AddWalletBalance are correctly handled by your SQL codegen and do not introduce runtime errors.

## Individual Comments

### Comment 1
<location> `service/wallet/internal/service/wallet_topup_test.go:131` </location>
<code_context>
+		wallet, err := st.topup.Topup(testCtx, topup)

 		assert.NoError(t, err)
+		assert.NotNil(t, wallet)
 	})
 }
</code_context>

<issue_to_address>
**suggestion (testing):** Success test now asserts returned wallet is not nil.

Add assertions for wallet fields such as ID, UserID, and Balance to verify the returned data is correct.

Suggested implementation:

```golang
		assert.NoError(t, err)
		assert.NotNil(t, wallet)
		assert.Equal(t, topup.WalletID, wallet.ID)
		assert.Equal(t, topup.UserID, wallet.UserID)
		assert.Equal(t, topup.Amount, wallet.Balance)
	})
}

```

If `createTestWallet()` returns a wallet with fields other than those in `topup`, you may need to adjust the expected values in the assertions to match the actual test wallet's fields (e.g., use `expectedWallet := createTestWallet()` and compare to its fields).
</issue_to_address>

### Comment 2
<location> `service/wallet/internal/grpc/handler/wallet_command_test.go:190-193` </location>
<code_context>
+
 		st := createWalletCommandSuite(ctrl)
-		st.topup.EXPECT().Topup(testCtxWithValidKey, gomock.Any()).Return(nil)
+		st.topup.EXPECT().Topup(testCtxWithValidKey, gomock.Any()).Return(&entity.Wallet{
+			ID:      walletID,
+			UserID:  userID,
+			Balance: decimal.NewFromFloat(10.23),
+		}, nil)
 		request := &apiv1.TopupWalletRequest{
</code_context>

<issue_to_address>
**suggestion (testing):** Success test now returns wallet entity from mock.

Add assertions to verify that the Data field in the response contains the expected wallet values.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread service/wallet/internal/service/wallet_topup_test.go
Comment thread service/wallet/internal/grpc/handler/wallet_command_test.go
@codecov

codecov Bot commented Nov 1, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 61.53846% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.04%. Comparing base (894d024) to head (531dfe9).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...rvice/wallet/internal/repository/db/queries.sql.go 0.00% 15 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #76      +/-   ##
==========================================
- Coverage   61.22%   61.04%   -0.19%     
==========================================
  Files          61       61              
  Lines        2094     2115      +21     
==========================================
+ Hits         1282     1291       +9     
- Misses        774      786      +12     
  Partials       38       38              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@qodo-code-review

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Validate non-nil wallet after update

In updateUserBalances, check that the wallets returned from AddWalletBalance are
not nil to handle cases where the update might silently fail to find a row.

service/wallet/internal/service/wallet_transferer.go [112-122]

 func (wt *WalletTransferer) updateUserBalances(ctx context.Context, transfer *entity.TransferWallet) error {
-	if _, err := wt.walletRepo.AddWalletBalance(ctx, transfer.SenderWalletID, transfer.Amount.Neg()); err != nil {
+	senderWallet, err := wt.walletRepo.AddWalletBalance(ctx, transfer.SenderWalletID, transfer.Amount.Neg())
+	if err != nil {
 		slog.ErrorContext(ctx, "[WalletTransferer-updateUserBalances] subtract sender balance fail", "error", err)
 		return err
 	}
-	if _, err := wt.walletRepo.AddWalletBalance(ctx, transfer.ReceiverWalletID, transfer.Amount); err != nil {
+	if senderWallet == nil {
+		err = entity.ErrInternal("sender wallet is nil after update")
+		slog.ErrorContext(ctx, "[WalletTransferer-updateUserBalances] subtract sender balance returns nil wallet", "error", err)
+		return err
+	}
+
+	receiverWallet, err := wt.walletRepo.AddWalletBalance(ctx, transfer.ReceiverWalletID, transfer.Amount)
+	if err != nil {
 		slog.ErrorContext(ctx, "[WalletTransferer-updateUserBalances] add receiver balance fail", "error", err)
+		return err
+	}
+	if receiverWallet == nil {
+		err = entity.ErrInternal("receiver wallet is nil after update")
+		slog.ErrorContext(ctx, "[WalletTransferer-updateUserBalances] add receiver balance returns nil wallet", "error", err)
 		return err
 	}
 	return nil
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the return value of AddWalletBalance is discarded and proposes adding a nil check, which is a valid defensive programming practice to prevent potential issues if the underlying repository returns nil, nil.

Medium
High-level
Standardize API response consistency

The TopupWallet endpoint was updated to return the modified entity, but other
mutation endpoints like TransferBalance and RegisterUser still return empty
responses. To improve API consistency, all mutation endpoints should adopt a
standard response format, such as returning the updated resource.

Examples:

service/wallet/api/v1/wallet.proto [114-117]
message TopupWalletResponse {
  // data represents wallet.
  Wallet data = 1 [(google.api.field_behavior) = OUTPUT_ONLY];
}
service/wallet/api/v1/wallet.proto [124]

Solution Walkthrough:

Before:

// service/wallet/api/v1/wallet.proto
message TopupWalletResponse {
  Wallet data = 1;
}
message TransferBalanceResponse {}

// service/user/api/v1/user.proto
message RegisterUserResponse {}

// service/wallet/internal/grpc/handler/wallet_command.go
func (wc *WalletCommand) TopupWallet(...) (*apiv1.TopupWalletResponse, error) {
    wallet, _ := wc.topup.Topup(...)
    return &apiv1.TopupWalletResponse{Data: createWalletProto(wallet)}, nil
}

func (wc *WalletCommand) TransferBalance(...) (*apiv1.TransferBalanceResponse, error) {
    // ... business logic
    return &apiv1.TransferBalanceResponse{}, nil
}

After:

// service/wallet/api/v1/wallet.proto
message TopupWalletResponse {
  Wallet data = 1;
}
message TransferBalanceResponse {
  Wallet sender_wallet = 1;
  Wallet receiver_wallet = 2;
}

// service/user/api/v1/user.proto
message RegisterUserResponse {
  User data = 1;
}

// service/wallet/internal/grpc/handler/wallet_command.go
func (wc *WalletCommand) TransferBalance(...) (*apiv1.TransferBalanceResponse, error) {
    // ... business logic to get updated wallets
    return &apiv1.TransferBalanceResponse{
        SenderWallet: createWalletProto(updatedSenderWallet),
        ReceiverWallet: createWalletProto(updatedReceiverWallet),
    }, nil
}
Suggestion importance[1-10]: 7

__

Why: This is a valid and important architectural suggestion that correctly identifies an API design inconsistency introduced by the PR, which could impact future development and client-side implementation.

Medium
  • More

@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: 0

🧹 Nitpick comments (3)
service/auth/test/integration/login_test.go (1)

17-22: Assert the registration succeeds before running the login cases

Line 21 currently drops both the response and error from RegisterAccount. If registration ever fails, the subsequent assertions will all report login failures, masking the real issue. Please capture the result and assert success so setup problems surface immediately.

-	_, _ = grpcClient.RegisterAccount(testCtxBasic, req)
+	res, err := grpcClient.RegisterAccount(testCtxBasic, req)
+	assert.NoError(t, err, "failed to register account for login test")
+	assert.NotEmpty(t, res)
service/wallet/internal/service/wallet_transferer_test.go (1)

201-201: Align mocks with the new AddWalletBalance contract.

Now that AddWalletBalance returns (*entity.Wallet, error), the happy-path expectations should hand back a non-nil wallet. Returning nil masks regressions if the service later starts using the updated balances. Please switch the successful expectations to return a stub wallet (e.g., createTestWallet()), keeping only the error paths at nil.

Also applies to: 220-221, 240-241, 260-261

service/wallet/internal/repository/postgres/wallet.go (1)

54-66: Consider populating Auditable fields from the query result.

The query returns created_at, updated_at, created_by, and updated_by fields (lines 31-41 in queries.sql.go), but they're not mapped to the returned entity.Wallet. Since entity.Wallet embeds Auditable, consider populating these fields for consistency with the GetUserWalletForUpdate pattern.

Apply this diff if you want to include the audit fields:

 return &entity.Wallet{
 	ID:      res.ID,
 	UserID:  res.UserID,
 	Balance: res.Balance,
+	Auditable: entity.Auditable{
+		CreatedAt: res.CreatedAt,
+		UpdatedAt: res.UpdatedAt,
+		CreatedBy: res.CreatedBy,
+		UpdatedBy: res.UpdatedBy,
+	},
 }, nil
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 894d024 and 531dfe9.

⛔ Files ignored due to path filters (6)
  • go.work.sum is excluded by !**/*.sum
  • proto/api/v1/auth_grpc.pb.go is excluded by !**/*.pb.go
  • proto/api/v1/transaction_grpc.pb.go is excluded by !**/*.pb.go
  • proto/api/v1/user_grpc.pb.go is excluded by !**/*.pb.go
  • proto/api/v1/wallet.pb.go is excluded by !**/*.pb.go
  • proto/api/v1/wallet_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (22)
  • openapiv2/arjuna.swagger.yaml (7 hunks)
  • service/auth/api/v1/auth.proto (2 hunks)
  • service/auth/test/integration/helper_test.go (0 hunks)
  • service/auth/test/integration/login_test.go (4 hunks)
  • service/auth/test/integration/register_test.go (2 hunks)
  • service/transaction/api/v1/transaction.proto (1 hunks)
  • service/user/api/v1/user.proto (3 hunks)
  • service/user/test/integration/helper_test.go (0 hunks)
  • service/user/test/integration/register_test.go (2 hunks)
  • service/wallet/api/v1/wallet.proto (3 hunks)
  • service/wallet/db/queries/queries.sql (1 hunks)
  • service/wallet/internal/grpc/handler/wallet_command.go (2 hunks)
  • service/wallet/internal/grpc/handler/wallet_command_test.go (3 hunks)
  • service/wallet/internal/repository/db/queries.sql.go (2 hunks)
  • service/wallet/internal/repository/postgres/wallet.go (1 hunks)
  • service/wallet/internal/repository/postgres/wallet_test.go (2 hunks)
  • service/wallet/internal/service/wallet_topup.go (2 hunks)
  • service/wallet/internal/service/wallet_topup_test.go (4 hunks)
  • service/wallet/internal/service/wallet_transferer.go (2 hunks)
  • service/wallet/internal/service/wallet_transferer_test.go (4 hunks)
  • service/wallet/test/mock/service/wallet_topup.go (2 hunks)
  • service/wallet/test/mock/service/wallet_transferer.go (1 hunks)
💤 Files with no reviewable changes (2)
  • service/user/test/integration/helper_test.go
  • service/auth/test/integration/helper_test.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2024-09-28T10:58:40.061Z
Learnt from: indrasaputra
Repo: indrasaputra/arjuna PR: 56
File: service/user/internal/service/user_registrar_test.go:131-131
Timestamp: 2024-09-28T10:58:40.061Z
Learning: When mocking transactions in tests, using `context.WithValue` to pass transaction information is acceptable.

Applied to files:

  • service/wallet/internal/repository/postgres/wallet_test.go
  • service/wallet/test/mock/service/wallet_transferer.go
  • service/wallet/internal/service/wallet_transferer_test.go
  • service/wallet/internal/grpc/handler/wallet_command_test.go
  • service/wallet/test/mock/service/wallet_topup.go
📚 Learning: 2025-01-18T13:42:29.344Z
Learnt from: indrasaputra
Repo: indrasaputra/arjuna PR: 60
File: service/wallet/internal/service/wallet_transferer.go:84-93
Timestamp: 2025-01-18T13:42:29.344Z
Learning: When comparing UUIDs from github.com/google/uuid package, use bytes.Compare on the underlying byte slice (uuid[:]) instead of String() comparison to ensure correct ordering based on binary representation.

Applied to files:

  • service/wallet/internal/grpc/handler/wallet_command_test.go
  • service/auth/test/integration/register_test.go
📚 Learning: 2025-01-18T13:49:05.587Z
Learnt from: indrasaputra
Repo: indrasaputra/arjuna PR: 60
File: service/wallet/db/queries/queries.sql:8-9
Timestamp: 2025-01-18T13:49:05.587Z
Learning: In wallet-related SQL queries, while using just the ID is technically sufficient for identifying records, including user_id in WHERE clauses provides an additional layer of security to prevent unauthorized modifications.

Applied to files:

  • service/wallet/db/queries/queries.sql
🧬 Code graph analysis (11)
service/wallet/internal/repository/postgres/wallet.go (4)
service/wallet/internal/repository/db/models.go (1)
  • Wallet (14-24)
service/wallet/entity/wallet.go (1)
  • Wallet (11-16)
service/wallet/internal/repository/db/queries.sql.go (1)
  • AddWalletBalanceParams (22-25)
service/wallet/entity/error.go (1)
  • ErrInternal (12-22)
service/wallet/test/mock/service/wallet_transferer.go (2)
service/wallet/internal/repository/db/models.go (1)
  • Wallet (14-24)
service/wallet/entity/wallet.go (1)
  • Wallet (11-16)
service/wallet/internal/service/wallet_topup.go (2)
service/wallet/entity/wallet.go (2)
  • TopupWallet (19-24)
  • Wallet (11-16)
service/wallet/entity/error.go (1)
  • ErrEmptyWallet (38-53)
service/wallet/internal/service/wallet_topup_test.go (1)
service/wallet/entity/error.go (1)
  • ErrEmptyWallet (38-53)
service/wallet/internal/grpc/handler/wallet_command.go (2)
proto/api/v1/wallet.pb.go (9)
  • Topup (438-446)
  • Topup (459-459)
  • Topup (474-476)
  • TopupWalletResponse (245-251)
  • TopupWalletResponse (264-264)
  • TopupWalletResponse (279-281)
  • Wallet (374-384)
  • Wallet (397-397)
  • Wallet (412-414)
service/wallet/entity/wallet.go (1)
  • Wallet (11-16)
service/wallet/internal/grpc/handler/wallet_command_test.go (2)
proto/api/v1/wallet.pb.go (6)
  • Topup (438-446)
  • Topup (459-459)
  • Topup (474-476)
  • Wallet (374-384)
  • Wallet (397-397)
  • Wallet (412-414)
service/wallet/entity/wallet.go (1)
  • Wallet (11-16)
service/wallet/internal/service/wallet_transferer.go (2)
service/wallet/internal/repository/db/models.go (1)
  • Wallet (14-24)
service/wallet/entity/wallet.go (1)
  • Wallet (11-16)
service/auth/test/integration/register_test.go (1)
proto/api/v1/auth.pb.go (3)
  • Account (292-304)
  • Account (317-317)
  • Account (332-334)
service/auth/test/integration/login_test.go (1)
proto/api/v1/auth.pb.go (6)
  • RegisterAccountRequest (209-215)
  • RegisterAccountRequest (228-228)
  • RegisterAccountRequest (243-245)
  • Account (292-304)
  • Account (317-317)
  • Account (332-334)
service/wallet/test/mock/service/wallet_topup.go (2)
service/wallet/internal/service/wallet_topup.go (1)
  • TopupWallet (14-18)
service/wallet/entity/wallet.go (2)
  • TopupWallet (19-24)
  • Wallet (11-16)
service/wallet/internal/repository/db/queries.sql.go (3)
service/wallet/internal/repository/db/db.go (1)
  • Queries (24-26)
service/wallet/internal/repository/postgres/wallet.go (1)
  • Wallet (17-19)
service/wallet/internal/repository/db/models.go (1)
  • Wallet (14-24)
🪛 Checkov (3.2.334)
openapiv2/arjuna.swagger.yaml

[medium] 34-58: Ensure that operation objects have 'consumes' field defined for PUT, POST and PATCH operations - version 2.0 files

(CKV_OPENAPI_17)

⏰ 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). (77)
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Go code quality check / Semgrep scan
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / go-code-lint
  • GitHub Check: Go code quality check / Download go module
  • GitHub Check: Sourcery review
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Go code quality check / Semgrep scan
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / go-code-lint
  • GitHub Check: Go code quality check / Download go module
  • GitHub Check: Sourcery review
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Go code quality check / Semgrep scan
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / go-code-lint
  • GitHub Check: Go code quality check / Download go module
  • GitHub Check: Sourcery review
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Go code quality check / Semgrep scan
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / go-code-lint
  • GitHub Check: Sourcery review
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Go code quality check / Semgrep scan
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / go-code-lint
  • GitHub Check: Sourcery review
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Go code quality check / Semgrep scan
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / go-code-lint
  • GitHub Check: Sourcery review
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / go-code-lint
  • GitHub Check: Sourcery review
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / go-code-lint
  • GitHub Check: Sourcery review
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / go-code-lint
  • GitHub Check: Sourcery review
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Compile binary / Compile go binary
  • GitHub Check: Go code quality check / Unit test and coverage
  • GitHub Check: Go code quality check / go-code-lint
  • GitHub Check: Sourcery review
  • GitHub Check: Go code quality check / Unit test and coverage
🔇 Additional comments (18)
service/wallet/db/queries/queries.sql (1)

8-10: RETURNING clause looks solid.

Switching AddWalletBalance to :one with RETURNING * cleanly enables the repository to surface the updated wallet row; no issues spotted.

service/wallet/api/v1/wallet.proto (1)

34-42: Proto updates match the new contract.

Exposing TopupWalletResponse.data as an OUTPUT_ONLY wallet payload dovetails with the handler changes and keeps the schema consistent with swagger. Looks good.

Also applies to: 68-96, 114-117

service/wallet/internal/grpc/handler/wallet_command.go (1)

73-79: Handler wiring cleanly surfaces the wallet payload.

Capturing the wallet from wc.topup.Topup and funnelling it through createWalletProto is exactly what the new API surface needs; the helper keeps the conversion tidy.

Also applies to: 125-130

openapiv2/arjuna.swagger.yaml (1)

358-363: Swagger definition stays in lockstep.

Adding the data property referencing v1Wallet keeps the OpenAPI spec aligned with the proto/handler response change. Nicely synchronized.

service/wallet/internal/repository/db/queries.sql.go (1)

16-43: LGTM! SQL query correctly updated to return wallet data.

The change from :exec to :one with a RETURNING clause is appropriate for returning the updated wallet row. The scan operation correctly maps all returned columns to the Wallet struct fields.

service/wallet/test/mock/service/wallet_topup.go (2)

48-54: LGTM! Mock updated correctly for new return signature.

The mock method now correctly returns (*entity.Wallet, error) to match the updated interface.


87-93: LGTM! AddWalletBalance mock updated correctly.

The mock repository method signature and return handling are correct for the new (*entity.Wallet, error) return type.

service/wallet/internal/repository/postgres/wallet_test.go (2)

105-118: LGTM! Error test case correctly updated.

The test now uses ExpectQuery instead of ExpectExec and properly asserts that the wallet is nil on error.


120-140: LGTM! Success test case validates returned wallet.

The test correctly mocks the RETURNING clause with all wallet columns and validates the returned wallet's ID, UserID, and Balance fields.

service/wallet/internal/grpc/handler/wallet_command_test.go (2)

159-183: LGTM! Error test cases updated correctly.

The mock now returns (nil, error) to match the new signature, and assertions correctly verify nil response.


185-206: LGTM! Success test case validates wallet return.

The test correctly mocks a wallet return with proper ID, UserID, and Balance fields, and the request uses the same walletID for consistency.

service/wallet/internal/service/wallet_topup_test.go (4)

42-50: LGTM! Test correctly validates nil wallet on error.

The test now captures the wallet return value and asserts it's nil when an error occurs.


52-72: LGTM! Error cases handle wallet return correctly.

All error paths properly assert that the wallet is nil alongside the error.


110-120: LGTM! Repository error case updated correctly.

The mock now returns (nil, error) and the test validates nil wallet on error.


122-132: LGTM! Success case validates wallet return.

The mock returns a populated wallet and the test correctly asserts the wallet is not nil on success.

service/wallet/test/mock/service/wallet_transferer.go (1)

86-92: LGTM! Mock correctly updated for new signature.

The mock method now returns (*entity.Wallet, error) with proper type assertions.

service/wallet/internal/service/wallet_topup.go (2)

13-24: LGTM! Interfaces updated to return wallet data.

The TopupWallet and TopupWalletRepository interfaces now correctly return (*entity.Wallet, error) to support returning the updated wallet.


45-66: LGTM! Implementation correctly returns wallet data.

The Topup method now returns the updated wallet on success and (nil, error) on all error paths, which is the correct pattern for this change.

@indrasaputra
indrasaputra merged commit b49a2a8 into main Nov 1, 2025
112 checks passed
@indrasaputra
indrasaputra deleted the e2e branch November 1, 2025 08:11
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