mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 17:20:30 +00:00
This commit consolidates several improvements to satisfy SonarCloud Quality Gate and enhance the overall plugin stability: - Code Quality & Refactoring: - Reduced duplication to 0% by refactoring SyncManager and SyncStatusView. - Implemented centralized 'processBatch' logic for multi-file sync operations. - Fixed security hotspots by replacing legacy atob/btoa with Buffer. - Added memory safety limits to the diff algorithm to prevent DoS. - Test Coverage & Reliability: - Improved SyncManager coverage to 81.8% and GitignoreManager to 92.8%. - Added comprehensive unit tests for batch operations and complex .gitignore patterns. - Resolved all ESLint 'unsafe-member-access' and 'unbound-method' warnings in tests. - Infrastructure & Metadata: - Consolidated GitHub Actions into a single optimized 'ci.yml' using latest versions (v6-v8). - Synchronized manifest.json and versions.json to v1.1.0. - Fixed workflow permission issues identified by CodeQL.
131 lines
4.9 KiB
TypeScript
131 lines
4.9 KiB
TypeScript
/* eslint-disable @typescript-eslint/unbound-method */
|
|
import { describe, it, expect, vi, beforeEach, Mocked } from 'vitest';
|
|
import { SyncManager } from '../../src/logic/sync-manager';
|
|
import { App, DataAdapter } from 'obsidian';
|
|
import { GitLabFilesPushSettings } from '../../src/settings';
|
|
import { GitServiceInterface } from '../../src/services/git-service-interface';
|
|
|
|
vi.mock('obsidian');
|
|
|
|
describe('SyncManager Batch Operations', () => {
|
|
let manager: SyncManager;
|
|
let mockApp: Mocked<App>;
|
|
let mockGitService: Mocked<GitServiceInterface>;
|
|
let mockSettings: GitLabFilesPushSettings;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
|
|
const mockAdapter = {
|
|
exists: vi.fn(),
|
|
read: vi.fn(),
|
|
write: vi.fn(),
|
|
} as unknown as Mocked<DataAdapter>;
|
|
|
|
mockApp = {
|
|
vault: {
|
|
read: vi.fn(),
|
|
modify: vi.fn(),
|
|
getFileByPath: vi.fn(),
|
|
adapter: mockAdapter,
|
|
},
|
|
workspace: {
|
|
getActiveFile: vi.fn(),
|
|
detachLeavesOfType: vi.fn(),
|
|
}
|
|
} as unknown as Mocked<App>;
|
|
|
|
mockGitService = {
|
|
pushFile: vi.fn(),
|
|
getFile: vi.fn(),
|
|
testConnection: vi.fn(),
|
|
listFiles: vi.fn(),
|
|
deleteFile: vi.fn(),
|
|
getRepoGitignores: vi.fn(),
|
|
updateConfig: vi.fn(),
|
|
} as unknown as Mocked<GitServiceInterface>;
|
|
|
|
mockSettings = {
|
|
serviceType: 'github',
|
|
githubToken: 'token',
|
|
githubOwner: 'owner',
|
|
githubRepo: 'repo',
|
|
branch: 'main',
|
|
syncMetadata: {},
|
|
} as unknown as GitLabFilesPushSettings;
|
|
|
|
manager = new SyncManager(mockApp, mockGitService, mockSettings);
|
|
// @ts-ignore - accessing private for test
|
|
manager.saveSettings = vi.fn().mockResolvedValue(undefined);
|
|
});
|
|
|
|
describe('pushAllFiles', () => {
|
|
it('should push multiple files correctly', async () => {
|
|
const files = ['file1.md', 'file2.md'];
|
|
const adapter = mockApp.vault.adapter as Mocked<DataAdapter>;
|
|
|
|
vi.mocked(adapter.exists).mockResolvedValue(true);
|
|
vi.mocked(adapter.read).mockResolvedValue('content');
|
|
|
|
vi.mocked(mockGitService.getFile).mockResolvedValue({ content: '', sha: 'old-sha' });
|
|
vi.mocked(mockGitService.pushFile).mockResolvedValue('path');
|
|
|
|
const results = await manager.pushAllFiles(files);
|
|
|
|
expect(results.success).toBe(2);
|
|
expect(vi.mocked(mockGitService.pushFile)).toHaveBeenCalledTimes(2);
|
|
expect(vi.mocked(adapter.read)).toHaveBeenCalledWith('file1.md');
|
|
expect(vi.mocked(adapter.read)).toHaveBeenCalledWith('file2.md');
|
|
});
|
|
|
|
it('should handle failures during batch push', async () => {
|
|
const files = ['good.md', 'bad.md'];
|
|
const adapter = mockApp.vault.adapter as Mocked<DataAdapter>;
|
|
|
|
vi.mocked(adapter.exists).mockResolvedValue(true);
|
|
vi.mocked(adapter.read).mockResolvedValue('content');
|
|
|
|
vi.mocked(mockGitService.getFile).mockResolvedValue({ content: '', sha: 'old-sha' });
|
|
|
|
vi.mocked(mockGitService.pushFile)
|
|
.mockResolvedValueOnce('path')
|
|
.mockRejectedValueOnce(new Error('Push failed'));
|
|
|
|
const results = await manager.pushAllFiles(files);
|
|
|
|
expect(results.success).toBe(1);
|
|
expect(results.failed).toBe(1);
|
|
expect(results.errors[0]!.file).toBe('bad.md');
|
|
});
|
|
});
|
|
|
|
describe('pullAllAllFiles', () => {
|
|
it('should pull multiple files correctly', async () => {
|
|
const files = ['file1.md', 'file2.md'];
|
|
const adapter = mockApp.vault.adapter as Mocked<DataAdapter>;
|
|
|
|
vi.mocked(mockGitService.getFile).mockResolvedValue({ content: 'remote content', sha: 'new-sha' });
|
|
vi.mocked(adapter.exists).mockResolvedValue(true);
|
|
|
|
const results = await manager.pullAllFiles(files);
|
|
|
|
expect(results.success).toBe(2);
|
|
expect(vi.mocked(adapter.write)).toHaveBeenCalledTimes(2);
|
|
expect(vi.mocked(adapter.write)).toHaveBeenCalledWith('file1.md', 'remote content');
|
|
});
|
|
|
|
it('should handle missing remote files during batch pull', async () => {
|
|
const files = ['exists.md', 'missing.md'];
|
|
|
|
vi.mocked(mockGitService.getFile)
|
|
.mockResolvedValueOnce({ content: 'content', sha: 'sha' })
|
|
.mockResolvedValueOnce({ content: '', sha: '' });
|
|
|
|
const results = await manager.pullAllFiles(files);
|
|
|
|
expect(results.success).toBe(1);
|
|
expect(results.failed).toBe(1);
|
|
expect(results.errors[0]!.error).toContain('File not found in remote');
|
|
});
|
|
});
|
|
});
|