Skip to content

Conversation

@d-gubert
Copy link
Member

@d-gubert d-gubert commented Sep 18, 2025

Proposed changes (including videos or screenshots)

Making full document updates makes the system more sensitive do race conditions, as data can be overriden by their outdated counterparts.

This could cause an invalid app signature to be stored and lead to the app being marked as invalid_installation_disabled during server startup

By only updating the fields we need to modify, we reduce the chances of being hit by side effects of race conditions

Issue(s)

Steps to test or reproduce

Further comments

ARCH-1796

Summary by CodeRabbit

  • New Features
    • Added targeted operations to delete apps and update status, settings, app info, and marketplace data; partial updates now return the updated record and can explicitly unset permission flags.
  • Refactor
    • Switched to partial, targeted persistence to avoid unintended full-document mutations and reduce update surface.
  • Tests
    • Expanded unit tests for partial updates, permission-unset behavior, targeted field updates, and deletion.
  • Chores
    • Added a changeset documenting the update strategy.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Sep 18, 2025

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is targeting the wrong base branch. It should target 7.12.0, but it targets 7.11.0

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Sep 18, 2025

🦋 Changeset detected

Latest commit: 11ec639

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 43 packages
Name Type
@rocket.chat/apps-engine Patch
@rocket.chat/meteor Patch
@rocket.chat/apps Patch
@rocket.chat/core-services Patch
@rocket.chat/core-typings Patch
@rocket.chat/fuselage-ui-kit Patch
@rocket.chat/rest-typings Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/freeswitch Patch
@rocket.chat/gazzodown Patch
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/model-typings Patch
@rocket.chat/ui-avatar Patch
@rocket.chat/ui-client Patch
@rocket.chat/ui-contexts Patch
@rocket.chat/web-ui-registration Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/federation-service Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/stream-hub-service Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/license Patch
@rocket.chat/media-calls Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/models Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Patch
@rocket.chat/ui-voip Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

Walkthrough

Replaces full-document app storage updates with targeted partial-update methods across EE storage, engine abstractions, managers, migrations, and tests; removes legacy full-update signature and adds updatePartialAndReturnDocument, updateStatus, updateSetting, updateAppInfo, updateMarketplaceInfo, and remove.

Changes

Cohort / File(s) Summary
EE Storage Implementation
apps/meteor/ee/server/apps/storage/AppRealStorage.ts
Adds updatePartialAndReturnDocument, updateStatus, updateSetting, updateAppInfo, updateMarketplaceInfo, and remove; enforces _id for partial updates; always applies $set for provided fields and conditionally $unset permissionsGranted; returns updated document or boolean outcomes.
EE Storage Tests
apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts
Replaces loose mock DB with typed mockDb; adds tests for remove, updatePartialAndReturnDocument (including _id validation and unsetPermissionsGranted behavior), updateStatus, updateSetting, updateAppInfo, and updateMarketplaceInfo.
Migrations Update Path
apps/meteor/server/startup/migrations/v294.ts, apps/meteor/server/startup/migrations/v307.ts
Replaces calls to update(...) with updatePartialAndReturnDocument(...) while preserving payload and surrounding control flow.
Apps Engine Manager (Runtime)
packages/apps-engine/src/server/AppManager.ts
Replaces full-document writes with updateStatus, updatePartialAndReturnDocument, and updateMarketplaceInfo; separates status persistence from partial updates; adjusts some catch handlers and consolidates updates for signature/migrated/marketplace fields.
Apps Engine Settings Manager
packages/apps-engine/src/server/managers/AppSettingsManager.ts
Uses local storageItem, computes decoratedSetting.updatedAt, writes setting via updateSetting(_id, setting), removes direct rl.setStorageItem, retains change notifications and ONSETTINGUPDATED call.
Apps Engine Storage Abstraction
packages/apps-engine/src/server/storage/AppMetadataStorage.ts
Removes abstract update; adds abstract updatePartialAndReturnDocument(item, options?), updateStatus, updateSetting, updateAppInfo, and updateMarketplaceInfo; adds related type imports.
Apps Engine Settings Tests
packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts
Adds _id to mock storage item; replaces mocked update with updateSetting(appId, setting) and updates spies/assertions accordingly.
Changeset
.changeset/gorgeous-cougars-visit.md
Adds a changeset documenting the switch to partial updates and patches @rocket.chat/apps-engine and @rocket.chat/meteor.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant AM as AppManager
  participant S as AppMetadataStorage
  participant DB as DB

  Note over AM,S: Persist status only (targeted)
  AM->>S: updateStatus(_id, status)
  S->>DB: updateOne({ _id }, { $set: { status } })
  DB-->>S: { modifiedCount }
  S-->>AM: boolean

  Note over AM,S: Partial update and return document
  AM->>S: updatePartialAndReturnDocument({ _id, signature?, migrated?, marketplaceInfo? }, { unsetPermissionsGranted? })
  S->>DB: findOneAndUpdate({ _id }, { $set: fields, $unset?: { permissionsGranted: "" } }, { returnDocument: "after" })
  DB-->>S: updatedDocument
  S-->>AM: IAppStorageItem
Loading
sequenceDiagram
  autonumber
  participant ASM as AppSettingsManager
  participant RL as RuntimeLayer
  participant S as AppMetadataStorage
  participant DB as DB

  ASM->>RL: getStorageItem()
  RL-->>ASM: storageItem
  ASM->>S: updateSetting(storageItem._id, decoratedSetting)
  S->>DB: updateOne({ _id }, { $set: { "settings.<id>": decoratedSetting } })
  DB-->>S: { modifiedCount }
  S-->>ASM: boolean
  ASM->>RL: call(ONSETTINGUPDATED, decoratedSetting)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • ggazzo
  • scuciatto

Poem

I nibble at fields, not the whole,
I hop through records, tidy and droll.
I unset a flag with a gentle paw,
leave signatures safe — no accidental flaw.
Thump-thump! The DB smiles — hooray for small changes. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The introduction of a new remove(id) deletion method and its corresponding tests is not related to the ARCH-1796 objective of replacing full-document updates with targeted field updates and therefore falls outside the stated scope of this pull request. Extract the deletion logic into a separate pull request or clarify how the remove method supports the partial-update refactoring goals before merging.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “fix(apps): prevent unwanted changes to app status” refers to one specific aspect of the changeset but does not capture the broader refactoring of split update methods and partial field updates implemented across the storage layer, migrations, and managers.
Linked Issues Check ✅ Passed The pull request replaces full-document update calls with targeted field updates by introducing updatePartialAndReturnDocument and dedicated updateX methods across AppRealStorage, migrations, AppManager, and AppSettingsManager, aligning with ARCH-1796’s objectives to limit updates to specific fields and reduce race-condition risks.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/split-app-metadata-storage-update-method

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e3e3919 and 11ec639.

📒 Files selected for processing (1)
  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use descriptive test names that clearly communicate expected behavior

Applied to files:

  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts
🧬 Code graph analysis (1)
apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (1)
apps/meteor/ee/server/apps/storage/AppRealStorage.ts (1)
  • AppRealStorage (11-100)
⏰ 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). (7)
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 🔎 Code Check / Code Lint
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 🔨 Test Storybook / Test Storybook
  • GitHub Check: 📦 Meteor Build - coverage
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (8)
apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (8)

3-3: LGTM!

Import addition is appropriate for the new updateSetting test suite.


14-21: LGTM!

Explicit typing of mockDb improves test maintainability and the addition of updateOne stub supports the new targeted update methods.

Also applies to: 30-30, 33-33


136-145: LGTM!

Test correctly validates the idempotent design where remove returns { success: true } unconditionally to ensure removal goal, regardless of whether the document existed. Based on learnings.


147-218: Excellent edge case coverage!

The test suite thoroughly validates:

  • All _id validation scenarios (missing, null, undefined, empty string)
  • unsetPermissionsGranted behavior in all three states (true, false, absent)

Test expectations correctly align with the implementation's reference-based mutation behavior.


220-238: LGTM!

Test suite correctly validates both success and no-op scenarios for updateStatus, with proper assertions on the MongoDB query structure and return value based on modifiedCount.


240-276: LGTM!

Test suite properly validates updateSetting with comprehensive setting objects and correct nested path format (settings.${id}), covering both modified and unmodified scenarios.


278-328: LGTM!

Test suite correctly validates updateAppInfo with comprehensive IAppInfo objects, proper MongoDB query assertions, and coverage of both modified and unmodified cases.


330-404: LGTM!

Test suite properly validates updateMarketplaceInfo with comprehensive marketplace info arrays, correct MongoDB query structure, and coverage of both success and no-op scenarios.


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
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

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

@d-gubert d-gubert force-pushed the feat/split-app-metadata-storage-update-method branch from 21841e3 to 1385656 Compare September 18, 2025 19:52
@codecov
Copy link

codecov bot commented Sep 18, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.38%. Comparing base (5e821ad) to head (11ec639).
⚠️ Report is 11 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #36990      +/-   ##
===========================================
+ Coverage    67.37%   67.38%   +0.01%     
===========================================
  Files         3330     3330              
  Lines       113482   113565      +83     
  Branches     20598    20599       +1     
===========================================
+ Hits         76454    76531      +77     
- Misses       34418    34423       +5     
- Partials      2610     2611       +1     
Flag Coverage Δ
e2e 57.28% <ø> (-0.04%) ⬇️
unit 71.17% <100.00%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@d-gubert d-gubert force-pushed the feat/split-app-metadata-storage-update-method branch 5 times, most recently from 59bcc99 to 5911502 Compare September 24, 2025 19:25
@d-gubert d-gubert changed the title feat: split update method in AppMetadataStorage chore: split update method in AppMetadataStorage Sep 24, 2025
@d-gubert d-gubert force-pushed the feat/split-app-metadata-storage-update-method branch 4 times, most recently from c2fc4e0 to 8c2f214 Compare September 29, 2025 14:42
@d-gubert d-gubert marked this pull request as ready for review September 29, 2025 18:32
@d-gubert d-gubert requested review from a team as code owners September 29, 2025 18:32
@d-gubert d-gubert added this to the 7.12.0 milestone Sep 29, 2025
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

🧹 Nitpick comments (4)
apps/meteor/ee/server/apps/storage/AppRealStorage.ts (4)

73-76: Consider adding updatedAt timestamp for consistency.

The method correctly updates the status field, but it doesn't set updatedAt. For audit trail consistency and to maintain the pattern established in the create method, consider tracking modification time.

Apply this diff:

 public async updateStatus(_id: string, status: AppStatus): Promise<boolean> {
-  const result = await this.db.updateOne({ _id }, { $set: { status } });
+  const result = await this.db.updateOne({ _id }, { $set: { status, updatedAt: new Date() } });
   return result.modifiedCount > 0;
 }

78-82: Consider adding updatedAt timestamp for consistency.

The method correctly updates a specific setting using dot notation, but like updateStatus, it doesn't set updatedAt for audit tracking.

Apply this diff:

 public async updateSetting(_id: string, setting: ISetting): Promise<boolean> {
-  const result = await this.db.updateOne({ _id }, { $set: { [`settings.${setting.id}`]: setting } });
+  const result = await this.db.updateOne({ _id }, { $set: { [`settings.${setting.id}`]: setting, updatedAt: new Date() } });
 
   return result.modifiedCount > 0;
 }

84-87: Consider adding updatedAt timestamp for consistency.

The method correctly replaces the info object, but should set updatedAt to maintain the audit trail pattern.

Apply this diff:

 public async updateAppInfo(_id: string, info: IAppInfo): Promise<boolean> {
-  const result = await this.db.updateOne({ _id }, { $set: { info } });
+  const result = await this.db.updateOne({ _id }, { $set: { info, updatedAt: new Date() } });
   return result.modifiedCount > 0;
 }

89-92: Consider adding updatedAt timestamp for consistency.

The method correctly replaces the marketplaceInfo array, but should set updatedAt to maintain the audit trail pattern.

Apply this diff:

 public async updateMarketplaceInfo(_id: string, marketplaceInfo: IMarketplaceInfo[]): Promise<boolean> {
-  const result = await this.db.updateOne({ _id }, { $set: { marketplaceInfo } });
+  const result = await this.db.updateOne({ _id }, { $set: { marketplaceInfo, updatedAt: new Date() } });
   return result.modifiedCount > 0;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 48d1442 and 8c2f214.

📒 Files selected for processing (8)
  • apps/meteor/ee/server/apps/storage/AppRealStorage.ts (2 hunks)
  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (3 hunks)
  • apps/meteor/server/startup/migrations/v294.ts (1 hunks)
  • apps/meteor/server/startup/migrations/v307.ts (1 hunks)
  • packages/apps-engine/src/server/AppManager.ts (8 hunks)
  • packages/apps-engine/src/server/managers/AppSettingsManager.ts (2 hunks)
  • packages/apps-engine/src/server/storage/AppMetadataStorage.ts (2 hunks)
  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts (5 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use descriptive test names that clearly communicate expected behavior

Applied to files:

  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts
  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Utilize Playwright fixtures (test, page, expect) consistently

Applied to files:

  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts
  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use test.beforeAll() and test.afterAll() for setup and teardown

Applied to files:

  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Maintain test isolation between test cases

Applied to files:

  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts
  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.{ts,tsx,js,jsx} : Write concise, technical TypeScript/JavaScript with accurate typing

Applied to files:

  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts
  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use expect matchers (toEqual, toContain, toBeTruthy, toHaveLength, etc.) instead of assert statements

Applied to files:

  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure tests run reliably in parallel without shared state conflicts

Applied to files:

  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts
  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.{ts,tsx,js,jsx} : Follow DRY by extracting reusable logic into helper functions or page objects

Applied to files:

  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure a clean state for each test execution

Applied to files:

  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts
🧬 Code graph analysis (1)
apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (1)
apps/meteor/ee/server/apps/storage/AppRealStorage.ts (1)
  • AppRealStorage (11-93)
🔇 Additional comments (32)
packages/apps-engine/src/server/managers/AppSettingsManager.ts (2)

37-50: Partial update implementation looks correct.

The change correctly uses updateSetting for a targeted partial update, which aligns with the PR's goal of reducing race condition risk. The local mutation of storageItem.settings[decoratedSetting.id] at line 48 maintains consistency between in-memory state and persisted storage.


54-54: New post-update callback added.

The addition of ONSETTINGUPDATED callback provides a symmetrical hook to complement ON_PRE_SETTING_UPDATE, allowing apps to react after a setting has been persisted.

packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts (6)

1-1: LGTM: Import addition for enhanced matching.

The Any matcher is added to support flexible assertion matching in line 143.


4-4: LGTM: Type import for new API.

The ISetting type import aligns with the new updateSetting method signature that takes a setting object.


37-37: LGTM: Mock data enhanced with _id.

Adding _id to the mock storage item supports the new partial update API that requires document identifiers.


59-62: LGTM: Mock storage updated to new API.

The mock correctly reflects the new updateSetting(appId: string, setting: ISetting): Promise<boolean> signature, replacing the old full-document update method.


130-130: LGTM: Spy updated for new method.

The spy now targets updateSetting instead of the removed update method.


143-144: LGTM: Assertion validates new API contract.

The test correctly verifies that updateSetting is called with the app's _id and the setting object, ensuring the transition from full-document updates to field-specific updates.

apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (8)

3-3: LGTM: Import for test data construction.

SettingType is needed to construct valid ISetting objects in the updateSetting tests.


14-21: LGTM: Enhanced mock database structure.

The explicit typing of mockDb stubs improves type safety and makes the test setup clearer. The updateOne stub supports the new partial update methods.

Also applies to: 30-30


136-145: LGTM: Remove method test updated.

The test correctly validates that remove calls deleteOne and returns { success: true }.


147-186: LGTM: Comprehensive coverage of updatePartialAndReturnDocument.

The tests cover all three cases:

  1. unsetPermissionsGranted: true - correctly validates $unset operation
  2. unsetPermissionsGranted: false - validates standard $set without $unset
  3. No options - validates default behavior

This ensures the conditional unset logic works correctly.


188-206: LGTM: updateStatus tests validate boolean return.

Tests correctly verify both success (modifiedCount > 0) and failure (modifiedCount = 0) cases, ensuring callers can detect when status updates don't take effect.


208-244: LGTM: updateSetting tests validate nested path updates.

The tests correctly verify that settings are updated using the nested path settings.${setting.id} rather than replacing the entire settings object. This is crucial for the race condition mitigation strategy.


246-296: LGTM: updateAppInfo tests cover success and failure.

Both test cases validate the expected MongoDB update query structure and boolean return values based on modifiedCount.


298-372: LGTM: updateMarketplaceInfo tests provide comprehensive coverage.

The tests validate updating marketplace info with realistic data structures and verify both success and failure scenarios.

packages/apps-engine/src/server/storage/AppMetadataStorage.ts (6)

2-5: LGTM: Type imports for new method signatures.

The imported types support the new granular update methods, enabling type-safe partial updates.


24-27: LGTM: Partial update with optional unset behavior.

The updatePartialAndReturnDocument method signature correctly:

  • Takes Partial<IAppStorageItem> to support partial updates
  • Includes unsetPermissionsGranted option for conditional field removal
  • Returns the updated document for immediate use

This addresses the race condition risk by allowing selective field updates rather than full document replacement.


29-29: LGTM: Granular status update method.

Using _id as the identifier and returning a boolean success indicator provides a clean API for status-only updates, reducing the risk of overwriting other fields during concurrent operations.


31-31: LGTM: Setting-specific update method.

This method enables updating a single setting without touching other app metadata, directly addressing the race condition concern mentioned in the PR objectives.


33-33: LGTM: App info update method.

Granular update for app info field reduces race condition exposure.


35-35: LGTM: Marketplace info update method.

Field-specific update for marketplace information aligns with the PR's objective of reducing race conditions.

packages/apps-engine/src/server/AppManager.ts (9)

465-465: LGTM: Granular status update reduces race condition risk.

Replacing the full document update with updateStatus ensures only the status field is modified, preventing concurrent operations from overwriting other fields with stale data.


498-498: LGTM: Status-only update in disable flow.

Consistent use of updateStatus for granular updates.


517-517: LGTM: Error suppression for non-critical validation.

The .catch(() => {}) is appropriate here as license validation failures don't prevent the migration flow.


522-523: LGTM: Selective field update in migrate method.

Destructuring { marketplaceInfo, signature, migrated, _id } and passing only these fields to updatePartialAndReturnDocument ensures the update touches only the necessary fields, significantly reducing race condition exposure compared to the previous full-document update.


751-753: LGTM: Partial update with conditional unset.

Using updatePartialAndReturnDocument with the unsetPermissionsGranted option allows the update to conditionally remove the permissionsGranted field when undefined is passed, while only updating the fields present in the descriptor. This is a cleaner approach than full document replacement.


908-908: LGTM: Marketplace-specific update method.

Using updateMarketplaceInfo for targeted updates prevents overwriting unrelated app metadata during marketplace info synchronization.


944-944: LGTM: Status update with error handling.

The .catch(console.error) provides visibility into update failures while allowing the process to continue.


1075-1075: LGTM: Status persistence in initialization.

Granular status update reduces race condition risk during app initialization.


1179-1179: LGTM: Status update in enableApp.

Consistent use of updateStatus for status persistence.

apps/meteor/ee/server/apps/storage/AppRealStorage.ts (1)

1-4: LGTM! Type imports are appropriate.

The new type imports correctly support the granular update methods introduced in this refactor.

@d-gubert d-gubert force-pushed the feat/split-app-metadata-storage-update-method branch from 8c2f214 to e507dc5 Compare September 30, 2025 13:44
@d-gubert d-gubert changed the title chore: split update method in AppMetadataStorage fix: split update method in AppMetadataStorage Sep 30, 2025
@ggazzo ggazzo changed the title fix: split update method in AppMetadataStorage fix(apps): prevent unwanted changes to app status Sep 30, 2025
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

♻️ Duplicate comments (1)
apps/meteor/ee/server/apps/storage/AppRealStorage.ts (1)

60-74: Fix missing updatedAt and clarify permissionsGranted handling.

  1. Major: The method does not set updatedAt, which is inconsistent with the create method (Line 18). Partial updates should also track when the document was last modified.

  2. Minor: The permissionsGranted deletion logic on Line 69 mutates item after it has been assigned to updateQuery.$set (Line 65). While this works because JavaScript objects are passed by reference, it is fragile and unclear. It would be clearer to exclude permissionsGranted from the destructured item when unsetPermissionsGranted is true.

Apply this diff to fix the missing updatedAt and improve clarity:

 public async updatePartialAndReturnDocument(
   { _id, ...item }: IAppStorageItem,
   { unsetPermissionsGranted = false } = {},
 ): Promise<IAppStorageItem> {
+  const updateFields = { ...item };
+  if (unsetPermissionsGranted) {
+    delete updateFields.permissionsGranted;
+  }
+
   const updateQuery: UpdateFilter<IAppStorageItem> = {
-    $set: item,
+    $set: {
+      ...updateFields,
+      updatedAt: new Date(),
+    },
   };
 
   if (unsetPermissionsGranted) {
-    delete item.permissionsGranted;
     updateQuery.$unset = { permissionsGranted: 1 };
   }
 
   return this.db.findOneAndUpdate({ _id }, updateQuery, { returnDocument: 'after' });
 }
🧹 Nitpick comments (4)
packages/apps-engine/src/server/AppManager.ts (4)

465-465: Consider logging errors for observability.

The empty catch silently swallows any database errors during status persistence. While this is marked as intentional (fire-and-forget), logging the error would improve operational visibility without blocking the flow.

-			await this.appMetadataStorage.updateStatus(storageItem._id, storageItem.status).catch(() => {});
+			await this.appMetadataStorage.updateStatus(storageItem._id, storageItem.status).catch(console.error);

498-498: Consider logging errors for observability.

Same pattern as Line 465 - the empty catch silently swallows database errors. Consider logging for operational visibility.

-		await this.appMetadataStorage.updateStatus(storageItem._id, storageItem.status).catch(() => {});
+		await this.appMetadataStorage.updateStatus(storageItem._id, storageItem.status).catch(console.error);

517-517: Consider logging license validation errors.

The empty catch silently swallows license validation errors. If validation fails, the app continues to be marked as migrated, which might not be the desired behavior. Consider logging the error or handling validation failure explicitly.

-		await app.validateLicense().catch(() => {});
+		await app.validateLicense().catch(console.error);

1179-1179: Consider logging errors for observability.

Consistent with Lines 465 and 498, the empty catch silently swallows database errors. Consider logging for operational visibility.

-			await this.appMetadataStorage.updateStatus(storageItem._id, storageItem.status).catch(() => {});
+			await this.appMetadataStorage.updateStatus(storageItem._id, storageItem.status).catch(console.error);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8c2f214 and e507dc5.

📒 Files selected for processing (9)
  • .changeset/gorgeous-cougars-visit.md (1 hunks)
  • apps/meteor/ee/server/apps/storage/AppRealStorage.ts (2 hunks)
  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (3 hunks)
  • apps/meteor/server/startup/migrations/v294.ts (1 hunks)
  • apps/meteor/server/startup/migrations/v307.ts (1 hunks)
  • packages/apps-engine/src/server/AppManager.ts (8 hunks)
  • packages/apps-engine/src/server/managers/AppSettingsManager.ts (2 hunks)
  • packages/apps-engine/src/server/storage/AppMetadataStorage.ts (2 hunks)
  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/apps-engine/tests/server/managers/AppSettingsManager.spec.ts
  • apps/meteor/server/startup/migrations/v307.ts
  • apps/meteor/server/startup/migrations/v294.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use descriptive test names that clearly communicate expected behavior

Applied to files:

  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts
📚 Learning: 2025-09-30T13:00:05.440Z
Learnt from: d-gubert
PR: RocketChat/Rocket.Chat#36990
File: apps/meteor/ee/server/apps/storage/AppRealStorage.ts:55-58
Timestamp: 2025-09-30T13:00:05.440Z
Learning: In AppRealStorage (apps/meteor/ee/server/apps/storage/AppRealStorage.ts), the `remove` method is designed to be idempotent and returns `{ success: true }` unconditionally because the goal is to ensure the app is removed, not to distinguish whether this specific call performed the deletion. Database errors will throw exceptions.

Applied to files:

  • apps/meteor/ee/server/apps/storage/AppRealStorage.ts
🧬 Code graph analysis (1)
apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (1)
apps/meteor/ee/server/apps/storage/AppRealStorage.ts (1)
  • AppRealStorage (11-96)
⏰ 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). (4)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: Builds matrix rust bindings against alpine
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (21)
.changeset/gorgeous-cougars-visit.md (1)

1-7: LGTM!

The changeset correctly documents the patch-level changes to both packages and provides a clear description that aligns with the PR objectives.

packages/apps-engine/src/server/managers/AppSettingsManager.ts (3)

37-39: LGTM!

Caching storageItem locally and reading oldSetting from it ensures consistent reads and aligns with the partial update strategy.


47-48: Good: updatedAt timestamp and in-memory update.

Setting updatedAt on the decorated setting before persisting is correct and maintains consistency with the create method pattern. The in-memory mutation of storageItem.settings is appropriate since the storage update on Line 50 will persist the change.


50-54: LGTM! Aligns with partial update strategy.

The call to updateSetting replaces the previous full-document update pattern and correctly persists only the modified setting. The subsequent ONSETTINGUPDATED notification ensures app lifecycle hooks are invoked after persistence.

apps/meteor/ee/server/apps/storage/AppRealStorage.ts (4)

1-4: LGTM!

The new type imports correctly support the granular update methods introduced in this PR.


76-79: LGTM!

The updateStatus method correctly performs a targeted status update and returns a boolean indicating whether the document was modified, aligning with the partial update strategy.


81-85: LGTM!

The updateSetting method correctly updates a single nested setting using the settings.${setting.id} path, which is the appropriate way to perform granular updates on nested objects in MongoDB.


87-95: LGTM!

Both updateAppInfo and updateMarketplaceInfo methods correctly perform targeted field updates and return boolean values indicating modification success, consistent with the other update methods.

apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (7)

3-3: LGTM!

The test setup correctly adds the necessary imports, stubs, and type definitions to support the new storage methods.

Also applies to: 14-21, 30-33


136-145: LGTM!

The test correctly validates the idempotent remove method, which unconditionally returns { success: true } as per the design. Based on learnings.


147-186: Tests align with implementation but should validate updatedAt.

The tests correctly validate the updatePartialAndReturnDocument method's behavior, including the unsetPermissionsGranted option. However, they do not assert that updatedAt is set, which should be added after the implementation is fixed to include updatedAt (as noted in the review of AppRealStorage.ts).


188-206: LGTM!

The updateStatus tests correctly validate both success (modifiedCount > 0) and no-op (modifiedCount === 0) scenarios, ensuring the method returns the appropriate boolean value.


208-244: LGTM!

The updateSetting tests correctly validate the nested path update (settings.test-setting) and return value logic for both modified and unmodified scenarios.


246-296: LGTM!

The updateAppInfo tests comprehensively validate the info field update and boolean return logic for both success and no-op cases.


298-372: LGTM!

The updateMarketplaceInfo tests thoroughly validate the array field update and boolean return logic for both modified and unmodified scenarios.

packages/apps-engine/src/server/storage/AppMetadataStorage.ts (2)

2-5: LGTM!

The new type imports correctly support the granular update method signatures introduced in this file.


29-35: LGTM!

The new abstract methods for targeted updates have consistent signatures, taking _id and the respective data type, and returning a boolean to indicate modification success. This aligns well with the partial update strategy.

packages/apps-engine/src/server/AppManager.ts (4)

522-523: LGTM! Excellent use of targeted partial updates.

The code correctly extracts only the necessary fields (marketplaceInfo, signature, migrated, _id) for the partial update, avoiding full document replacement and reducing race condition risks. This aligns perfectly with the PR objectives.


908-908: LGTM! Proper use of targeted update method.

The code correctly uses the new updateMarketplaceInfo method to update only the marketplace information field, avoiding full document replacement. This aligns with the PR objectives to reduce race condition risks.


944-944: LGTM! Proper error handling with logging.

This updateStatus call correctly handles errors with console.error, providing operational visibility while maintaining the fire-and-forget pattern.


751-753: Restrict fields in updatePartialAndReturnDocument
The call at Line 751 passes the full descriptor into updatePartialAndReturnDocument(…), which will persist every provided field (and unset permissionsGranted when undefined). Unlike the targeted update on Line 523, this may overwrite unchanged properties and risk race conditions. If you truly intend to update all descriptor fields here, add a clarifying comment; otherwise, pass only the specific properties that need updating.

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: 1

♻️ Duplicate comments (1)
apps/meteor/ee/server/apps/storage/AppRealStorage.ts (1)

60-78: Critical: Fix permissionsGranted deletion logic and add updatedAt.

The current implementation has several issues:

  1. Critical: Line 69 assigns $set: item, capturing all fields at that moment. Line 73 then deletes item.permissionsGranted, but this mutation doesn't affect the already-constructed updateQuery.$set. The result is that MongoDB will process both $set: { permissionsGranted: [...] } and $unset: { permissionsGranted: 1 }, which is inefficient and confusing.

  2. Major: Missing updatedAt timestamp. The create method sets both createdAt and updatedAt (lines 17-18), but this update method doesn't maintain updatedAt, breaking consistency.

Apply this diff to fix both issues:

 public async updatePartialAndReturnDocument(
   { _id, ...item }: IAppStorageItem,
   { unsetPermissionsGranted = false } = {},
 ): Promise<IAppStorageItem> {
   if (!_id) {
     throw new Error('Property _id is required to update an app storage item');
   }

+  if (unsetPermissionsGranted) {
+    delete item.permissionsGranted;
+  }
+
   const updateQuery: UpdateFilter<IAppStorageItem> = {
     $set: {
+      ...item,
+      updatedAt: new Date(),
-      item,
     },
   };

   if (unsetPermissionsGranted) {
-    delete item.permissionsGranted;
     updateQuery.$unset = { permissionsGranted: 1 };
   }

   return this.db.findOneAndUpdate({ _id }, updateQuery, { returnDocument: 'after' });
 }

This ensures:

  • permissionsGranted is excluded from $set before constructing the update query when unsetPermissionsGranted is true
  • updatedAt is always set to track modification time
  • The object spread creates a clean copy for $set
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between af683a7 and e3e3919.

📒 Files selected for processing (3)
  • apps/meteor/ee/server/apps/storage/AppRealStorage.ts (2 hunks)
  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (3 hunks)
  • packages/apps-engine/src/server/AppManager.ts (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/apps-engine/src/server/AppManager.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use descriptive test names that clearly communicate expected behavior

Applied to files:

  • apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts
📚 Learning: 2025-09-30T13:00:05.440Z
Learnt from: d-gubert
PR: RocketChat/Rocket.Chat#36990
File: apps/meteor/ee/server/apps/storage/AppRealStorage.ts:55-58
Timestamp: 2025-09-30T13:00:05.440Z
Learning: In AppRealStorage (apps/meteor/ee/server/apps/storage/AppRealStorage.ts), the `remove` method is designed to be idempotent and returns `{ success: true }` unconditionally because the goal is to ensure the app is removed, not to distinguish whether this specific call performed the deletion. Database errors will throw exceptions.

Applied to files:

  • apps/meteor/ee/server/apps/storage/AppRealStorage.ts
🧬 Code graph analysis (1)
apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (1)
apps/meteor/ee/server/apps/storage/AppRealStorage.ts (1)
  • AppRealStorage (11-100)
⏰ 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). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (11)
apps/meteor/ee/tests/unit/server/apps/AppRealStorage.spec.ts (5)

136-145: LGTM!

The test correctly validates the idempotent behavior of remove, expecting { success: true } regardless of whether a document was deleted.


212-230: LGTM!

The tests correctly validate updateStatus behavior, returning true when a document is modified and false otherwise.


232-268: LGTM!

The tests correctly validate updateSetting behavior, including the use of the nested path 'settings.${setting.id}' for targeted updates.


270-320: LGTM!

The tests correctly validate updateAppInfo behavior for both successful and unsuccessful modifications.


322-396: LGTM!

The tests correctly validate updateMarketplaceInfo behavior for both successful and unsuccessful modifications.

apps/meteor/ee/server/apps/storage/AppRealStorage.ts (6)

1-4: LGTM!

The new type imports support the targeted update methods, aligning with the PR's objective to replace full-document updates with field-specific operations.


55-58: LGTM!

The remove method correctly implements idempotent deletion behavior, returning { success: true } to indicate the app is removed (regardless of whether this call performed the deletion). Database errors will throw exceptions.

Based on learnings.


80-83: LGTM!

The updateStatus method correctly implements a targeted field update, reducing race condition risk by updating only the status field.


85-89: LGTM!

The updateSetting method correctly uses MongoDB dot notation to update a specific nested setting, avoiding full-document replacement.


91-94: LGTM!

The updateAppInfo method correctly implements a targeted field update for the info object.


96-99: LGTM!

The updateMarketplaceInfo method correctly implements a targeted field update for marketplace metadata.

@ggazzo ggazzo added the stat: QA assured Means it has been tested and approved by a company insider label Oct 3, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Oct 3, 2025
@ggazzo ggazzo merged commit f627e67 into develop Oct 3, 2025
51 of 52 checks passed
@ggazzo ggazzo deleted the feat/split-app-metadata-storage-update-method branch October 3, 2025 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants