Skip to content

test: add unit tests for MCP config-migrator module#502

Open
nikolasdehor wants to merge 3 commits intoSynkraAI:mainfrom
nikolasdehor:test/config-migrator-coverage
Open

test: add unit tests for MCP config-migrator module#502
nikolasdehor wants to merge 3 commits intoSynkraAI:mainfrom
nikolasdehor:test/config-migrator-coverage

Conversation

@nikolasdehor
Copy link
Contributor

@nikolasdehor nikolasdehor commented Feb 24, 2026

Resumo

  • 48 testes unitários para .aios-core/core/mcp/config-migrator.js
  • Isolamento completo via mocks de global-config-manager e symlink-manager
  • Cobre todas as 5 funções exportadas: detectProjectConfig, analyzeMigration, mergeServers, executeMigration, restoreFromBackup

Áreas testadas

  • detectProjectConfig: Prioridade de detecção em 4 caminhos, formato legado mcpServers, fallthrough em JSON inválido, contagem de servidores
  • analyzeMigration: Todos os 5 cenários de migração (already_linked, fresh_install, link_only, migrate_to_global, merge_required)
  • mergeServers: Resolução de conflitos add/skip/overwrite, garantia de imutabilidade, parâmetros padrão
  • executeMigration: Ciclo completo de migração com tratamento de erros em cada etapa (estrutura, config, symlink, backup)
  • restoreFromBackup: Remoção de symlink vs diretório, erros de rename, detecção de backup ausente

Plano de teste

  • Todos os 48 testes passam localmente
  • Dependências totalmente mockadas (sem efeitos colaterais no filesystem)

Summary by CodeRabbit

  • Tests
    • Added a comprehensive test suite for configuration migration covering detection, analysis, merging, execution, rollback, backups, and error paths. Tests validate many scenarios and edge cases (existing links, fresh installs, merge/overwrite behavior, empty inputs, invalid/missing configs), exercise recommended options/messages/stats, and use mocks/spies to simulate filesystem and external dependencies without real I/O.

48 tests covering:
- MIGRATION_OPTION constants
- detectProjectConfig: multi-path detection, legacy mcpServers format,
  invalid JSON fallthrough, server counting, default project root
- analyzeMigration: all 5 scenarios (already_linked, fresh_install,
  link_only, migrate_to_global, merge_required)
- mergeServers: add/skip/overwrite logic, conflict tracking,
  immutability of input objects, default parameters
- executeMigration: full lifecycle (structure creation, config creation,
  server migration, symlink creation, backup), error propagation
  for each stage, KEEP_PROJECT short-circuit
- restoreFromBackup: symlink removal, directory removal, rename errors,
  lstat errors, missing backup detection
Copilot AI review requested due to automatic review settings February 24, 2026 23:28
@vercel
Copy link

vercel bot commented Feb 24, 2026

@nikolasdehor is attempting to deploy a commit to the Pedro Valério Lopez's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Walkthrough

Adds a comprehensive unit test suite for the config-migrator module that validates detection, analysis, merging, execution, and rollback behaviors using mocked dependencies and extensive error-path coverage.

Changes

Cohort / File(s) Summary
Config Migrator Tests
tests/core/mcp/config-migrator.test.js
Adds a ~650+ line test suite covering MIGRATION_OPTION constants, detectProjectConfig (multiple locations, invalid JSON, fallbacks), analyzeMigration (link/migrate/merge scenarios), mergeServers (add/duplicate/overwrite, stats, non-mutation), executeMigration (flows for keep/migrate/merge/backup/link/error handling), and restoreFromBackup (rename/lstat/error cases). Heavy use of mocks/spies; no production API changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

tests, type: test, core, mcp, area: core

Suggested reviewers

  • oalanicolas
  • Pedrovaleriolopez
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately and concisely describes the main change: adding unit tests for the MCP config-migrator module.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@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

🧹 Nitpick comments (1)
tests/core/mcp/config-migrator.test.js (1)

40-47: Use resetAllMocks() to avoid implementation leakage between tests.

jest.clearAllMocks() resets call history only; mocked return values/implementations can persist across test cases and create order-dependent behavior in this suite.

Suggested change
 beforeEach(() => {
-  jest.clearAllMocks();
+  jest.resetAllMocks();

   // Default mocks
   getProjectMcpPath.mockReturnValue('/project/.aios-core/tools/mcp');
   checkLinkStatus.mockReturnValue({ status: LINK_STATUS.NOT_LINKED });
   globalConfigExists.mockReturnValue(false);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/mcp/config-migrator.test.js` around lines 40 - 47, Replace
jest.clearAllMocks() with jest.resetAllMocks() in the beforeEach block so mocked
implementations/return values are reset between tests; after calling
jest.resetAllMocks(), re-establish the default mocks for getProjectMcpPath,
checkLinkStatus, and globalConfigExists (e.g., mockReturnValue calls shown)
inside beforeEach so each test starts with the intended defaults.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/core/mcp/config-migrator.test.js`:
- Around line 550-556: The test should not assume executeMigration throws
synchronously; instead mock checkLinkStatus (used by analyzeMigration) to return
a rejected Promise and assert executeMigration rejects (or returns a structured
failure) using async assertions. Update the test named 'propagates exceptions
thrown before try/catch (analyzeMigration)' so
checkLinkStatus.mockImplementation(() => Promise.reject(new
Error('unexpected'))) and use await
expect(executeMigration('/project')).rejects.toThrow('unexpected') (or assert
the returned failure shape if your API returns an error object), referencing
checkLinkStatus, analyzeMigration, and executeMigration.

---

Nitpick comments:
In `@tests/core/mcp/config-migrator.test.js`:
- Around line 40-47: Replace jest.clearAllMocks() with jest.resetAllMocks() in
the beforeEach block so mocked implementations/return values are reset between
tests; after calling jest.resetAllMocks(), re-establish the default mocks for
getProjectMcpPath, checkLinkStatus, and globalConfigExists (e.g.,
mockReturnValue calls shown) inside beforeEach so each test starts with the
intended defaults.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9425a35 and 4d2c759.

📒 Files selected for processing (1)
  • tests/core/mcp/config-migrator.test.js

Comment on lines +550 to +556
test('propagates exceptions thrown before try/catch (analyzeMigration)', () => {
// analyzeMigration is called before the try/catch in executeMigration,
// so errors from it propagate as unhandled exceptions
checkLinkStatus.mockImplementation(() => { throw new Error('unexpected'); });

expect(() => executeMigration('/project')).toThrow('unexpected');
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid locking in unhandled exceptions as the expected API contract.

This test currently validates a throw path from executeMigration, which makes error handling brittle and discourages returning structured failures from a migration API.

Suggested test direction (paired with implementation hardening)
- expect(() => executeMigration('/project')).toThrow('unexpected');
+ const result = executeMigration('/project');
+ expect(result.success).toBe(false);
+ expect(result.errors).toContainEqual(expect.stringContaining('unexpected'));

As per coding guidelines, "Verify error handling is comprehensive."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/mcp/config-migrator.test.js` around lines 550 - 556, The test
should not assume executeMigration throws synchronously; instead mock
checkLinkStatus (used by analyzeMigration) to return a rejected Promise and
assert executeMigration rejects (or returns a structured failure) using async
assertions. Update the test named 'propagates exceptions thrown before try/catch
(analyzeMigration)' so checkLinkStatus.mockImplementation(() =>
Promise.reject(new Error('unexpected'))) and use await
expect(executeMigration('/project')).rejects.toThrow('unexpected') (or assert
the returned failure shape if your API returns an error object), referencing
checkLinkStatus, analyzeMigration, and executeMigration.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a dedicated Jest unit test suite for the MCP config-migrator module to validate migration detection, scenario analysis, merging behavior, execution flow, and backup restore logic while mocking external dependencies.

Changes:

  • Introduces 48 unit tests covering detectProjectConfig, analyzeMigration, mergeServers, executeMigration, and restoreFromBackup.
  • Adds dependency mocking for global-config-manager and symlink-manager to avoid filesystem/global state side effects.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

test('reports errors when structure creation fails', () => {
checkLinkStatus.mockReturnValue({ status: LINK_STATUS.NOT_LINKED });
globalConfigExists.mockReturnValue(false);
createGlobalStructure.mockReturnValue({ success: false, errors: ['disk full'] });
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The mocked return shape for createGlobalStructure here doesn’t match the real implementation. In .aios-core/core/mcp/global-config-manager.js, createGlobalStructure() returns errors as an array of objects like { dir, error } (and { file, error }), not strings. Using ['disk full'] risks masking a real issue in executeMigration where it does structureResult.errors.join(', ') (which would produce [object Object]). Consider mocking errors with the real object shape and asserting on the resulting message (or update production code to format the object errors).

Suggested change
createGlobalStructure.mockReturnValue({ success: false, errors: ['disk full'] });
createGlobalStructure.mockReturnValue({
success: false,
errors: [{ dir: '/global', error: 'disk full' }],
});

Copilot uses AI. Check for mistakes.
Comment on lines +518 to +529
test('backs up project directory when DIRECTORY status', () => {
const configPath = path.join('/project', 'mcp.json');
existsSyncSpy.mockImplementation((p) => p === configPath);
readFileSyncSpy.mockReturnValue(JSON.stringify({ servers: {} }));
checkLinkStatus.mockReturnValue({ status: LINK_STATUS.DIRECTORY });
globalConfigExists.mockReturnValue(true);
readGlobalConfig.mockReturnValue({ version: '1.0', servers: {} });
writeGlobalConfig.mockReturnValue({ success: true });
createLink.mockReturnValue({ success: true });
getProjectMcpPath.mockReturnValue('/project/.aios-core/tools/mcp');

const result = executeMigration('/project', MIGRATION_OPTION.MERGE);
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

This test sets checkLinkStatus to DIRECTORY, which in the real symlink-manager implies the project MCP path actually exists as a directory. However, the fs.existsSync mock only returns true for the config file (/project/mcp.json), and fs.renameSync is stubbed to always succeed. That combination can make the backup-path test pass even if backup logic would fail in a real DIRECTORY scenario. Consider making the mocks consistent (e.g., existsSync true for the project MCP path and/or have renameSync throw when the source path is missing) and assert renameSync was called with the expected projectMcpPath and backupPath.

Copilot uses AI. Check for mistakes.
- Troca clearAllMocks por resetAllMocks (evita leak de implementações)
- Usa shape real de errors do createGlobalStructure ({ dir, error })
- Adiciona asserção de argumentos no renameSync do backup DIRECTORY
Copy link

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

♻️ Duplicate comments (1)
tests/core/mcp/config-migrator.test.js (1)

559-565: Test documents brittle throw-through behavior — known concern from prior review.

This test locks in the fact that executeMigration propagates unhandled exceptions from analyzeMigration (called before the internal try/catch). While the test accurately describes current behavior, it codifies a fragile contract. Ideally, executeMigration would wrap the full flow in error handling and return a structured failure.

Since this is a test-only PR, the implementation change is out of scope — but this should be tracked as follow-up work. As per coding guidelines, "Verify error handling is comprehensive."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/mcp/config-migrator.test.js` around lines 559 - 565, Test
currently asserts that executeMigration allows exceptions from analyzeMigration
to propagate (via checkLinkStatus mock), which locks in brittle behavior; since
implementation changes are out of scope, mark this as a known-brittle test and
refrain from enforcing the throw-through contract: change the test to test.skip
(or prepend it with a clear TODO) and add a one-line comment referencing a
follow-up issue to make executeMigration handle analyzeMigration errors
comprehensively; mention executeMigration, analyzeMigration and checkLinkStatus
in the comment so the future fix can locate the behavior to change.
🧹 Nitpick comments (3)
tests/core/mcp/config-migrator.test.js (3)

39-47: Clean mock lifecycle — good use of resetAllMocks.

The beforeEach properly resets all mocks and re-establishes sensible defaults, preventing state leakage between tests. This is the right approach after switching from clearAllMocks.

Minor nit: Line 43 has a Portuguese comment ("re-estabelecidos após resetAllMocks"). Consider keeping comments in English for consistency across the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/mcp/config-migrator.test.js` around lines 39 - 47, Replace the
Portuguese inline comment in the beforeEach block with an English equivalent to
keep code comments consistent; specifically update the comment near the mock
re-establishment (adjacent to getProjectMcpPath.mockReturnValue,
checkLinkStatus.mockReturnValue, and globalConfigExists.mockReturnValue) from
"re-estabelecidos após resetAllMocks" to a clear English phrase like
"re-established after resetAllMocks".

191-196: Default parameter test could be slightly more assertive.

The comment says "Just verify it doesn't throw," but the test does assert result.found === false. Consider also verifying that existsSyncSpy was called with a path derived from process.cwd() to confirm the default is actually wired up:

💡 Optional strengthening
   test('uses process.cwd() as default project root', () => {
     existsSyncSpy.mockReturnValue(false);
     const result = detectProjectConfig();
     expect(result.found).toBe(false);
-    // Just verify it doesn't throw
+    expect(existsSyncSpy).toHaveBeenCalledWith(
+      expect.stringContaining(process.cwd()),
+    );
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/mcp/config-migrator.test.js` around lines 191 - 196, Update the
test "uses process.cwd() as default project root" to not only assert
result.found is false but also verify that existsSyncSpy was invoked with a path
derived from process.cwd(); call detectProjectConfig() and assert that
existsSyncSpy received a path containing process.cwd() (use process.cwd(),
detectProjectConfig, and existsSyncSpy as the referenced symbols) so the test
confirms the default root wiring.

473-488: Portuguese comments — and a useful source-code bug observation.

Lines 485–486 document an apparent bug in the production code where errors (which are objects with { dir, error } shape) are .join(', ')-ed, producing [object Object]. This is valuable insight, but the comments should be in English for the team:

🌐 Suggested English translation
-      // NOTA: config-migrator usa .join(', ') em objetos, resultando em [object Object]
-      // Isso é um bug no source — os errors deveriam ser mapeados antes do join
+      // NOTE: config-migrator calls .join(', ') on objects, producing [object Object]
+      // This is a source bug — errors should be mapped to strings before join

Additionally, consider filing an issue to fix the serialization bug in the production code rather than leaving it as a test comment.

Would you like me to open an issue to track the [object Object] serialization bug in config-migrator.js?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/mcp/config-migrator.test.js` around lines 473 - 488, Replace the
Portuguese test comment lines that describe the serialization bug with an
English comment (e.g., note that createGlobalStructure returns error objects {
dir, error } and that config-migrator.js currently does .join(', ') on those
objects producing “[object Object]”), and then file a tracker issue for
config-migrator.js describing the bug and suggested fixes (map error objects to
strings or JSON.stringify before joining); reference createGlobalStructure and
executeMigration in the issue to help locate the problematic behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@tests/core/mcp/config-migrator.test.js`:
- Around line 559-565: Test currently asserts that executeMigration allows
exceptions from analyzeMigration to propagate (via checkLinkStatus mock), which
locks in brittle behavior; since implementation changes are out of scope, mark
this as a known-brittle test and refrain from enforcing the throw-through
contract: change the test to test.skip (or prepend it with a clear TODO) and add
a one-line comment referencing a follow-up issue to make executeMigration handle
analyzeMigration errors comprehensively; mention executeMigration,
analyzeMigration and checkLinkStatus in the comment so the future fix can locate
the behavior to change.

---

Nitpick comments:
In `@tests/core/mcp/config-migrator.test.js`:
- Around line 39-47: Replace the Portuguese inline comment in the beforeEach
block with an English equivalent to keep code comments consistent; specifically
update the comment near the mock re-establishment (adjacent to
getProjectMcpPath.mockReturnValue, checkLinkStatus.mockReturnValue, and
globalConfigExists.mockReturnValue) from "re-estabelecidos após resetAllMocks"
to a clear English phrase like "re-established after resetAllMocks".
- Around line 191-196: Update the test "uses process.cwd() as default project
root" to not only assert result.found is false but also verify that
existsSyncSpy was invoked with a path derived from process.cwd(); call
detectProjectConfig() and assert that existsSyncSpy received a path containing
process.cwd() (use process.cwd(), detectProjectConfig, and existsSyncSpy as the
referenced symbols) so the test confirms the default root wiring.
- Around line 473-488: Replace the Portuguese test comment lines that describe
the serialization bug with an English comment (e.g., note that
createGlobalStructure returns error objects { dir, error } and that
config-migrator.js currently does .join(', ') on those objects producing
“[object Object]”), and then file a tracker issue for config-migrator.js
describing the bug and suggested fixes (map error objects to strings or
JSON.stringify before joining); reference createGlobalStructure and
executeMigration in the issue to help locate the problematic behavior.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d2c759 and c96cb49.

📒 Files selected for processing (1)
  • tests/core/mcp/config-migrator.test.js

- Documentar no teste que analyzeMigration propaga exceções porque
  é chamado antes do try/catch (comportamento correto do source)
- Adicionar projectMcpPath no mock de existsSync para consistência
  no teste de backup com status DIRECTORY
Copy link

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

♻️ Duplicate comments (1)
tests/core/mcp/config-migrator.test.js (1)

560-567: ⚠️ Potential issue | 🟠 Major

Avoid locking unhandled throw behavior into the API contract

Line 560 and Line 566 currently assert a raw synchronous throw from executeMigration. This bakes in brittle pre-try/catch behavior and makes future hardening (structured failure result) a breaking test change. Prefer asserting a stable failure contract instead of the unhandled throw.

Suggested direction
- test('propaga exceções de analyzeMigration (chamado antes do try/catch)', () => {
-   checkLinkStatus.mockImplementation(() => { throw new Error('unexpected'); });
-   expect(() => executeMigration('/project')).toThrow('unexpected');
- });
+ test('returns failure result when analysis fails unexpectedly', () => {
+   checkLinkStatus.mockImplementation(() => { throw new Error('unexpected'); });
+   const result = executeMigration('/project');
+   expect(result.success).toBe(false);
+   expect(result.errors).toContainEqual(expect.stringContaining('unexpected'));
+ });

As per coding guidelines, "Verify error handling is comprehensive."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/mcp/config-migrator.test.js` around lines 560 - 567, The test
currently asserts that executeMigration('/project') throws synchronously when
checkLinkStatus (used by analyzeMigration) throws, which locks brittle
pre-try/catch behavior into the API; change the test to assert the stable
failure contract instead—mock checkLinkStatus (or analyzeMigration) to throw,
call executeMigration('/project'), and expect a structured failure result (e.g.,
an object like { success: false, errors: [...] } or whatever the module's
error-result shape is) rather than expecting a raw throw; reference
executeMigration, analyzeMigration, and checkLinkStatus to locate the behavior
to mock and the return value to assert.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@tests/core/mcp/config-migrator.test.js`:
- Around line 560-567: The test currently asserts that
executeMigration('/project') throws synchronously when checkLinkStatus (used by
analyzeMigration) throws, which locks brittle pre-try/catch behavior into the
API; change the test to assert the stable failure contract instead—mock
checkLinkStatus (or analyzeMigration) to throw, call
executeMigration('/project'), and expect a structured failure result (e.g., an
object like { success: false, errors: [...] } or whatever the module's
error-result shape is) rather than expecting a raw throw; reference
executeMigration, analyzeMigration, and checkLinkStatus to locate the behavior
to mock and the return value to assert.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c96cb49 and 58cd0cd.

📒 Files selected for processing (1)
  • tests/core/mcp/config-migrator.test.js

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants