fix(test): make plugin installation state test cross-platform

This commit is contained in:
777genius 2026-04-17 08:36:26 +03:00
parent bb60bbb0ec
commit aedca570e1

View file

@ -1,11 +1,21 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import * as fs from 'node:fs/promises'; import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { PluginInstallationStateService } from '@main/services/extensions/state/PluginInstallationStateService'; import { PluginInstallationStateService } from '@main/services/extensions/state/PluginInstallationStateService';
const TEST_ROOT = path.parse(process.cwd()).root || path.sep;
const MOCK_CLAUDE_BASE_PATH = path.join(TEST_ROOT, 'tmp', 'mock-claude');
const PROJECT_A_PATH = path.join(TEST_ROOT, 'tmp', 'project-a');
const PROJECT_B_PATH = path.join(TEST_ROOT, 'tmp', 'project-b');
function normalizeMockPath(filePath: string | URL): string {
return String(filePath).replaceAll('\\', '/');
}
// Mock pathDecoder to control ~/.claude path // Mock pathDecoder to control ~/.claude path
vi.mock('@main/utils/pathDecoder', () => ({ vi.mock('@main/utils/pathDecoder', () => ({
getClaudeBasePath: () => '/tmp/mock-claude', getClaudeBasePath: () => MOCK_CLAUDE_BASE_PATH,
})); }));
// Mock filesystem // Mock filesystem
@ -27,7 +37,7 @@ describe('PluginInstallationStateService', () => {
describe('getInstalledPlugins', () => { describe('getInstalledPlugins', () => {
it('returns user-scoped plugins enabled in user settings', async () => { it('returns user-scoped plugins enabled in user settings', async () => {
mockedFs.readFile.mockImplementation(async (filePath) => { mockedFs.readFile.mockImplementation(async (filePath) => {
const normalizedPath = String(filePath); const normalizedPath = normalizeMockPath(filePath);
if (normalizedPath.endsWith('/plugins/installed_plugins.json')) { if (normalizedPath.endsWith('/plugins/installed_plugins.json')) {
return JSON.stringify({ return JSON.stringify({
version: 2, version: 2,
@ -52,7 +62,7 @@ describe('PluginInstallationStateService', () => {
}); });
} }
if (normalizedPath === '/tmp/mock-claude/settings.json') { if (normalizedPath === normalizeMockPath(path.join(MOCK_CLAUDE_BASE_PATH, 'settings.json'))) {
return JSON.stringify({ return JSON.stringify({
enabledPlugins: { enabledPlugins: {
'context7@claude-plugins-official': true, 'context7@claude-plugins-official': true,
@ -75,7 +85,7 @@ describe('PluginInstallationStateService', () => {
it('includes project and local scopes only for the active project', async () => { it('includes project and local scopes only for the active project', async () => {
mockedFs.readFile.mockImplementation(async (filePath) => { mockedFs.readFile.mockImplementation(async (filePath) => {
const normalizedPath = String(filePath); const normalizedPath = normalizeMockPath(filePath);
if (normalizedPath.endsWith('/plugins/installed_plugins.json')) { if (normalizedPath.endsWith('/plugins/installed_plugins.json')) {
return JSON.stringify({ return JSON.stringify({
version: 2, version: 2,
@ -105,7 +115,7 @@ describe('PluginInstallationStateService', () => {
}); });
} }
if (normalizedPath === '/tmp/mock-claude/settings.json') { if (normalizedPath === normalizeMockPath(path.join(MOCK_CLAUDE_BASE_PATH, 'settings.json'))) {
return JSON.stringify({ return JSON.stringify({
enabledPlugins: { enabledPlugins: {
'context7@claude-plugins-official': true, 'context7@claude-plugins-official': true,
@ -113,7 +123,7 @@ describe('PluginInstallationStateService', () => {
}); });
} }
if (normalizedPath === '/tmp/project-a/.claude/settings.json') { if (normalizedPath === normalizeMockPath(path.join(PROJECT_A_PATH, '.claude', 'settings.json'))) {
return JSON.stringify({ return JSON.stringify({
enabledPlugins: { enabledPlugins: {
'typescript-lsp@claude-plugins-official': true, 'typescript-lsp@claude-plugins-official': true,
@ -121,7 +131,10 @@ describe('PluginInstallationStateService', () => {
}); });
} }
if (normalizedPath === '/tmp/project-a/.claude/settings.local.json') { if (
normalizedPath ===
normalizeMockPath(path.join(PROJECT_A_PATH, '.claude', 'settings.local.json'))
) {
return JSON.stringify({ return JSON.stringify({
enabledPlugins: { enabledPlugins: {
'formatter@claude-plugins-official': true, 'formatter@claude-plugins-official': true,
@ -132,7 +145,7 @@ describe('PluginInstallationStateService', () => {
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }); throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
}); });
const entries = await service.getInstalledPlugins('/tmp/project-a'); const entries = await service.getInstalledPlugins(PROJECT_A_PATH);
expect(entries.map((entry) => [entry.pluginId, entry.scope])).toEqual([ expect(entries.map((entry) => [entry.pluginId, entry.scope])).toEqual([
['context7@claude-plugins-official', 'user'], ['context7@claude-plugins-official', 'user'],
@ -143,7 +156,7 @@ describe('PluginInstallationStateService', () => {
it('does not leak another project scope into the current project', async () => { it('does not leak another project scope into the current project', async () => {
mockedFs.readFile.mockImplementation(async (filePath) => { mockedFs.readFile.mockImplementation(async (filePath) => {
const normalizedPath = String(filePath); const normalizedPath = normalizeMockPath(filePath);
if (normalizedPath.endsWith('/plugins/installed_plugins.json')) { if (normalizedPath.endsWith('/plugins/installed_plugins.json')) {
return JSON.stringify({ return JSON.stringify({
version: 2, version: 2,
@ -166,7 +179,7 @@ describe('PluginInstallationStateService', () => {
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }); throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
}); });
const entries = await service.getInstalledPlugins('/tmp/project-b'); const entries = await service.getInstalledPlugins(PROJECT_B_PATH);
expect(entries).toEqual([]); expect(entries).toEqual([]);
}); });
@ -182,7 +195,7 @@ describe('PluginInstallationStateService', () => {
it('returns empty array for unexpected version', async () => { it('returns empty array for unexpected version', async () => {
mockedFs.readFile.mockImplementation(async (filePath) => { mockedFs.readFile.mockImplementation(async (filePath) => {
const normalizedPath = String(filePath); const normalizedPath = normalizeMockPath(filePath);
if (normalizedPath.endsWith('/plugins/installed_plugins.json')) { if (normalizedPath.endsWith('/plugins/installed_plugins.json')) {
return JSON.stringify({ version: 1, plugins: {} }); return JSON.stringify({ version: 1, plugins: {} });
} }
@ -198,7 +211,7 @@ describe('PluginInstallationStateService', () => {
it('caches within TTL', async () => { it('caches within TTL', async () => {
mockedFs.readFile.mockImplementation(async (filePath) => { mockedFs.readFile.mockImplementation(async (filePath) => {
const normalizedPath = String(filePath); const normalizedPath = normalizeMockPath(filePath);
if (normalizedPath.endsWith('/plugins/installed_plugins.json')) { if (normalizedPath.endsWith('/plugins/installed_plugins.json')) {
return JSON.stringify({ version: 2, plugins: {} }); return JSON.stringify({ version: 2, plugins: {} });
} }
@ -216,7 +229,7 @@ describe('PluginInstallationStateService', () => {
it('caches results independently per project path', async () => { it('caches results independently per project path', async () => {
mockedFs.readFile.mockImplementation(async (filePath) => { mockedFs.readFile.mockImplementation(async (filePath) => {
const normalizedPath = String(filePath); const normalizedPath = normalizeMockPath(filePath);
if (normalizedPath.endsWith('/plugins/installed_plugins.json')) { if (normalizedPath.endsWith('/plugins/installed_plugins.json')) {
return JSON.stringify({ version: 2, plugins: {} }); return JSON.stringify({ version: 2, plugins: {} });
} }
@ -226,8 +239,8 @@ describe('PluginInstallationStateService', () => {
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }); throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
}); });
await service.getInstalledPlugins('/tmp/project-a'); await service.getInstalledPlugins(PROJECT_A_PATH);
await service.getInstalledPlugins('/tmp/project-b'); await service.getInstalledPlugins(PROJECT_B_PATH);
expect(mockedFs.readFile).toHaveBeenCalledTimes(8); expect(mockedFs.readFile).toHaveBeenCalledTimes(8);
}); });