Skip to content

Feat/#11#20

Open
EmmanuelAR wants to merge 5 commits into
ShadeProtocol:mainfrom
EmmanuelAR:feat/#11
Open

Feat/#11#20
EmmanuelAR wants to merge 5 commits into
ShadeProtocol:mainfrom
EmmanuelAR:feat/#11

Conversation

@EmmanuelAR

@EmmanuelAR EmmanuelAR commented Jun 27, 2026

Copy link
Copy Markdown

Closes #11
Evidence: https://www.loom.com/share/92e6a0e28cf34c4fa02c902f0f2a5a66
Postman collection:
Shade - Merchant Profile API (#11).postman_collection.json

Summary by CodeRabbit

  • New Features

    • Added a “My Profile” view and edit flow for merchants, including new profile endpoints.
    • Merchant profile responses now include additional profile details and webhook information.
  • Bug Fixes

    • Improved update handling to accept partial changes, clear optional fields, and ignore non-editable fields.
    • Added stronger validation for profile updates, including HTTPS webhook checks and empty-request rejection.
  • Tests

    • Added integration and unit coverage for profile retrieval, updates, validation, and response shape.

- Added `getMyProfileController` and `updateMyProfileController` to handle fetching and updating the authenticated merchant's profile.
- Introduced validation for updating merchant profiles with `validateUpdateMerchant`.
- Updated routes to include new endpoints for profile management.
- Created integration and unit tests for the new profile functionalities, ensuring proper authentication and validation handling.
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6130924c-bf14-4b4a-be22-984bfc74c057

📥 Commits

Reviewing files that changed from the base of the PR and between a3c95f6 and 3395ebb.

📒 Files selected for processing (3)
  • tests/integration/merchant.profile.test.ts
  • tests/unit/merchant.profile.services.test.ts
  • tests/unit/merchant.update.validation.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/merchant.update.validation.test.ts
  • tests/unit/merchant.profile.services.test.ts
  • tests/integration/merchant.profile.test.ts

📝 Walkthrough

Walkthrough

Adds authenticated GET /merchants/me and PATCH /merchants/me endpoints. Introduces UpdateMerchantInput validation with HTTPS webhook enforcement, extends sanitizeMerchant to expose more profile fields, adds getMyProfile/updateMyProfile services, wires two new controllers and routes, and covers everything with unit and integration tests.

Changes

Merchant Profile API

Layer / File(s) Summary
Update validation contract
src/utils/validation.ts, tests/unit/merchant.update.validation.test.ts
Adds UpdateMerchantInput interface, editable/required field lists, isValidHttpsUrl helper, and validateUpdateMerchant with early-empty, text, logo, and webhook checks. Tests cover valid partials, non-editable field detection, whitespace rejection, null-clear semantics, and webhook URL enforcement.
Merchant profile services
src/services/merchant.services.ts, tests/unit/merchant.profile.services.test.ts
Extends sanitizeMerchant to include account, webhook, and additional profile fields. Adds getMyProfile (404 on missing) and updateMyProfile (trim, null-normalize, Prisma update, sanitize). Unit tests assert Prisma call shapes and sanitization behavior.
Controllers and route wiring
src/controllers/merchant.controllers.ts, src/routes/merchant.routes.ts, tests/integration/merchant.profile.test.ts
Adds getMyProfileController and updateMyProfileController with 401 auth guard, validation, AppError mapping, and 500 fallback. Registers GET /me and PATCH /me routes. Integration tests cover unauthenticated 401, valid updates, non-editable field ignoring, webhook validation, null-clear, and empty-payload rejection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • ShadeProtocol/shade-backend#18: Introduces the refresh-token–based session model and merchant schema fields that the new /merchants/me controllers and integration tests depend on.

Suggested reviewers

  • codebestia

🐇 A merchant asked, "Who am I?"
So I hopped to fetch and reply,
GET /me returns your name,
PATCH /me updates the frame—
No apiKeys exposed, oh my! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too vague and does not describe the merchant profile API changes. Use a descriptive title like 'Add merchant profile GET and PATCH endpoints'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The routes, validation, sanitization, and tests cover the protected merchant profile GET/PATCH flow and required response behavior.
Out of Scope Changes check ✅ Passed The changes stay focused on the merchant profile API and related tests, with no obvious unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

tests/integration/merchant.profile.test.ts

Oops! Something went wrong! :(

ESLint: 9.24.0

ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
at file:///eslint.config.js?mtime=1782586284384:1:24
at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
at async node:internal/modules/esm/loader:639:26
at async dynamicImportConfig (/node_modules/eslint/lib/config/config-loader.js:181:17)
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:271:9)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:584:23)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:765:23)
at async /node_modules/eslint/lib/eslint/eslint.js:746:6
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:743:19)

tests/unit/merchant.profile.services.test.ts

Oops! Something went wrong! :(

ESLint: 9.24.0

ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
at file:///eslint.config.js?mtime=1782586284384:1:24
at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
at async node:internal/modules/esm/loader:639:26
at async dynamicImportConfig (/node_modules/eslint/lib/config/config-loader.js:181:17)
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:271:9)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:584:23)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:765:23)
at async /node_modules/eslint/lib/eslint/eslint.js:746:6
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:743:19)

tests/unit/merchant.update.validation.test.ts

Oops! Something went wrong! :(

ESLint: 9.24.0

ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
at file:///eslint.config.js?mtime=1782586284384:1:24
at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
at async node:internal/modules/esm/loader:639:26
at async dynamicImportConfig (/node_modules/eslint/lib/config/config-loader.js:181:17)
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:271:9)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:584:23)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:765:23)
at async /node_modules/eslint/lib/eslint/eslint.js:746:6
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:743:19)


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

- Deleted the implementation plan and design spec for the Merchant Profile API as they are no longer relevant to the current project structure.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (5)
tests/integration/merchant.profile.test.ts (2)

56-59: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Assert the sensitive fields named in the API contract.

These checks cover relation leakage, but the linked issue specifically forbids exposing emailOtp and API-key hash fields. Adding assertions for those fields will keep the sanitizer contract from regressing silently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/merchant.profile.test.ts` around lines 56 - 59, The
integration test for the merchant profile response currently checks for some
hidden fields but misses the contract-specific sensitive fields. Update the
assertions in merchant.profile.test.ts around the response checks to also verify
that response.body does not expose emailOtp or API-key hash fields, alongside
the existing refreshTokens/apiKeys checks, so the sanitizer contract is covered
by the test.

85-99: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Cover all explicitly non-editable fields in the ignore-path test.

This only exercises address and email, but merchantId and account are also part of the immutable contract. Including them here would better protect against accidental writes to identity/account fields.

Suggested test expansion
     const response = await request(app)
       .patch(ME_URL)
       .set('Authorization', 'Bearer valid-token')
-      .send({ firstName: 'Grace', address: '0xHACK', email: 'evil@example.com' });
+      .send({
+        firstName: 'Grace',
+        address: '0xHACK',
+        email: 'evil@example.com',
+        merchantId: 999,
+        account: '0xHACKED',
+      });
 
     expect(response.status).toBe(200);
     const updateArg = prismaMock.merchant.update.mock.calls[0][0];
     expect(updateArg.data).toEqual({ firstName: 'Grace' });
     expect(response.body.address).toBe('0x123');
     expect(response.body.email).toBe('ada@example.com');
+    expect(response.body.merchantId).toBe(1);
+    expect(response.body.account).toBe('CCONTRACT');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/merchant.profile.test.ts` around lines 85 - 99, Expand the
ignore-path test in merchant.profile.test.ts so it also covers the other
immutable fields enforced by the merchant profile update flow. In the existing
silently ignores non-editable fields test around the PATCH to ME_URL, include
merchantId and account in the sent payload alongside address and email, and keep
asserting that prismaMock.merchant.update only receives editable data (via the
updateArg.data check) while the response still preserves the original
merchantId/account values. Use the existing merchant.profile test setup and
prismaMock.merchant.update mock to verify these fields are stripped before
persistence.
tests/unit/merchant.update.validation.test.ts (1)

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

Add logo: '' coverage for clear semantics.

The contract gives logo the same clear-to-null behavior as webhook, but this suite only locks in the webhook branch. A regression in validateUpdateMerchant({ logo: '' }) would currently slip through.

Suggested test
   test('accepts logo and webhook cleared with null', () => {
     expect(validateUpdateMerchant({ logo: null, webhook: null })).toEqual({});
   });
 
+  test('accepts an empty string logo as a clear', () => {
+    expect(validateUpdateMerchant({ logo: '' })).toEqual({});
+  });
+
   test('accepts an empty string webhook as a clear', () => {
     expect(validateUpdateMerchant({ webhook: '' })).toEqual({});
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/merchant.update.validation.test.ts` around lines 23 - 29, Add
coverage for the clear-to-null behavior of validateUpdateMerchant by extending
the existing validation tests in merchant.update.validation.test.ts to assert
that an empty string for logo is treated the same as webhook. Update or add a
test near the current validateUpdateMerchant cases so that
validateUpdateMerchant({ logo: '' }) returns an empty object, matching the clear
semantics already covered for webhook.
tests/unit/merchant.profile.services.test.ts (1)

7-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Sanitization assertions are vacuous because baseMerchant omits the sensitive fields.

baseMerchant has no refreshTokens/apiKeys, so expect(result).not.toHaveProperty('refreshTokens'/'apiKeys') (Lines 38-39, 83) passes trivially and does not actually verify the allowlist drops internal fields. Add the sensitive fields to the fixture so the assertions exercise the sanitizer.

🧪 Strengthen the fixture
   createdAt: new Date(),
   updatedAt: new Date(),
+  // internal fields that must be stripped by sanitizeMerchant
+  refreshTokens: [{ token: 'secret' }],
+  apiKeys: [{ hash: 'hashed' }],
+  emailOtp: '123456',
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/merchant.profile.services.test.ts` around lines 7 - 26, The
sanitizer tests are currently vacuous because the shared `baseMerchant` fixture
in `merchant.profile.services.test.ts` does not include sensitive fields, so the
`sanitizeMerchantProfile` assertions never prove they are removed. Update
`baseMerchant` to include internal properties like `refreshTokens` and
`apiKeys`, then keep the existing `expect(result).not.toHaveProperty(...)`
checks so they verify the allowlist behavior of `sanitizeMerchantProfile` and
the related service output.
src/controllers/merchant.controllers.ts (1)

66-110: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Both controllers correctly mirror the established auth/validation/error pattern. 401 guard, validateUpdateMerchant 400 path, and AppError→status / generic→500 mapping are consistent with registerMerchantController.

Optional: the try/catch + AppError/500 block is now duplicated across three controllers; extracting an asyncHandler/error-mapping wrapper would remove the repetition. Deferable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/merchant.controllers.ts` around lines 66 - 110, The
controllers already follow the expected auth, validation, and AppError/500
mapping pattern, so no functional change is needed in getMyProfileController or
updateMyProfileController. If you want to address the duplication noted in the
review, extract the repeated try/catch error mapping into a reusable async
handler or shared helper and have getMyProfileController,
updateMyProfileController, and registerMerchantController use it consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/controllers/merchant.controllers.ts`:
- Around line 66-110: The controllers already follow the expected auth,
validation, and AppError/500 mapping pattern, so no functional change is needed
in getMyProfileController or updateMyProfileController. If you want to address
the duplication noted in the review, extract the repeated try/catch error
mapping into a reusable async handler or shared helper and have
getMyProfileController, updateMyProfileController, and
registerMerchantController use it consistently.

In `@tests/integration/merchant.profile.test.ts`:
- Around line 56-59: The integration test for the merchant profile response
currently checks for some hidden fields but misses the contract-specific
sensitive fields. Update the assertions in merchant.profile.test.ts around the
response checks to also verify that response.body does not expose emailOtp or
API-key hash fields, alongside the existing refreshTokens/apiKeys checks, so the
sanitizer contract is covered by the test.
- Around line 85-99: Expand the ignore-path test in merchant.profile.test.ts so
it also covers the other immutable fields enforced by the merchant profile
update flow. In the existing silently ignores non-editable fields test around
the PATCH to ME_URL, include merchantId and account in the sent payload
alongside address and email, and keep asserting that prismaMock.merchant.update
only receives editable data (via the updateArg.data check) while the response
still preserves the original merchantId/account values. Use the existing
merchant.profile test setup and prismaMock.merchant.update mock to verify these
fields are stripped before persistence.

In `@tests/unit/merchant.profile.services.test.ts`:
- Around line 7-26: The sanitizer tests are currently vacuous because the shared
`baseMerchant` fixture in `merchant.profile.services.test.ts` does not include
sensitive fields, so the `sanitizeMerchantProfile` assertions never prove they
are removed. Update `baseMerchant` to include internal properties like
`refreshTokens` and `apiKeys`, then keep the existing
`expect(result).not.toHaveProperty(...)` checks so they verify the allowlist
behavior of `sanitizeMerchantProfile` and the related service output.

In `@tests/unit/merchant.update.validation.test.ts`:
- Around line 23-29: Add coverage for the clear-to-null behavior of
validateUpdateMerchant by extending the existing validation tests in
merchant.update.validation.test.ts to assert that an empty string for logo is
treated the same as webhook. Update or add a test near the current
validateUpdateMerchant cases so that validateUpdateMerchant({ logo: '' })
returns an empty object, matching the clear semantics already covered for
webhook.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 986e1cb3-78bf-447b-b05c-a19f26e03f37

📥 Commits

Reviewing files that changed from the base of the PR and between c0e30a5 and a3c95f6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • docs/superpowers/plans/2026-06-26-merchant-profile-api.md
  • docs/superpowers/specs/2026-06-26-merchant-profile-api-design.md
  • src/controllers/merchant.controllers.ts
  • src/routes/merchant.routes.ts
  • src/services/merchant.services.ts
  • src/utils/validation.ts
  • tests/integration/merchant.profile.test.ts
  • tests/unit/merchant.profile.services.test.ts
  • tests/unit/merchant.update.validation.test.ts

…ations

- Updated integration and unit tests for merchant profile to include internal relations like refreshTokens and apiKeys.
- Modified the test for non-editable fields to account for additional fields: merchantId and account.
- Added validation for accepting empty string values for logo and webhook in update validation tests.
@codebestia

Copy link
Copy Markdown
Contributor

@EmmanuelAR
Great job so far.

Please fix the CI.

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.

Merchant Profile API (Get & Update)

2 participants