mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 17:20:30 +00:00
refactor(test): extract shared SyncManager mock setup to reduce duplication
Move repeated beforeEach mock initialization into createSyncManagerMocks() helper to eliminate ~45 lines of copy-paste across binary and hidden test files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fcb9447733
commit
1f71a43980
3 changed files with 92 additions and 131 deletions
|
|
@ -1,72 +1,20 @@
|
|||
/* 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';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { createSyncManagerMocks, makeBuf, SyncManagerMocks } from './sync-manager-test-helpers';
|
||||
|
||||
vi.mock('obsidian');
|
||||
|
||||
describe('SyncManager – binary file handling', () => {
|
||||
let manager: SyncManager;
|
||||
let mockApp: Mocked<App>;
|
||||
let mockGitService: Mocked<GitServiceInterface>;
|
||||
let mockAdapter: Mocked<DataAdapter>;
|
||||
let mockSettings: GitLabFilesPushSettings;
|
||||
|
||||
const makeBuf = (bytes: number[]) => new Uint8Array(bytes).buffer;
|
||||
let mocks: SyncManagerMocks;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockAdapter = {
|
||||
exists: vi.fn(),
|
||||
read: vi.fn(),
|
||||
write: vi.fn(),
|
||||
readBinary: vi.fn(),
|
||||
writeBinary: vi.fn(),
|
||||
mkdir: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as Mocked<DataAdapter>;
|
||||
|
||||
mockApp = {
|
||||
vault: {
|
||||
read: vi.fn(),
|
||||
readBinary: vi.fn(),
|
||||
modify: vi.fn(),
|
||||
modifyBinary: vi.fn(),
|
||||
getFileByPath: vi.fn().mockReturnValue(null),
|
||||
adapter: mockAdapter,
|
||||
},
|
||||
} 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: {},
|
||||
vaultFolder: '',
|
||||
rootPath: '',
|
||||
} as unknown as GitLabFilesPushSettings;
|
||||
|
||||
manager = new SyncManager(mockApp, mockGitService, mockSettings);
|
||||
// @ts-ignore
|
||||
manager.saveSettings = vi.fn().mockResolvedValue(undefined);
|
||||
mocks = createSyncManagerMocks();
|
||||
});
|
||||
|
||||
describe('pushFile with binary path (string)', () => {
|
||||
it('reads via adapter.readBinary for binary extensions', async () => {
|
||||
const { manager, mockAdapter, mockGitService } = mocks;
|
||||
const buf = makeBuf([137, 80, 78, 71]);
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(true);
|
||||
vi.mocked(mockAdapter.readBinary).mockResolvedValue(buf);
|
||||
|
|
@ -83,6 +31,7 @@ describe('SyncManager – binary file handling', () => {
|
|||
});
|
||||
|
||||
it('skips push when binary content is already in sync', async () => {
|
||||
const { manager, mockAdapter, mockGitService } = mocks;
|
||||
const buf = makeBuf([1, 2, 3, 4]);
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(true);
|
||||
vi.mocked(mockAdapter.readBinary).mockResolvedValue(buf);
|
||||
|
|
@ -94,6 +43,7 @@ describe('SyncManager – binary file handling', () => {
|
|||
});
|
||||
|
||||
it('updates metadata when binary is already in sync', async () => {
|
||||
const { manager, mockAdapter, mockGitService, mockSettings } = mocks;
|
||||
const buf = makeBuf([1, 2, 3]);
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(true);
|
||||
vi.mocked(mockAdapter.readBinary).mockResolvedValue(buf);
|
||||
|
|
@ -109,6 +59,7 @@ describe('SyncManager – binary file handling', () => {
|
|||
|
||||
describe('pullFile with binary content', () => {
|
||||
it('writes via adapter.writeBinary when remote content is ArrayBuffer', async () => {
|
||||
const { manager, mockAdapter, mockGitService } = mocks;
|
||||
const buf = makeBuf([137, 80, 78, 71]);
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'bin-sha', content: buf });
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(false);
|
||||
|
|
@ -120,6 +71,7 @@ describe('SyncManager – binary file handling', () => {
|
|||
});
|
||||
|
||||
it('creates parent directory before writing binary', async () => {
|
||||
const { manager, mockAdapter, mockGitService } = mocks;
|
||||
const buf = makeBuf([255, 216, 255]);
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'bin-sha', content: buf });
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(false);
|
||||
|
|
@ -131,6 +83,7 @@ describe('SyncManager – binary file handling', () => {
|
|||
});
|
||||
|
||||
it('skips pull when binary content is already in sync', async () => {
|
||||
const { manager, mockAdapter, mockGitService, mockSettings } = mocks;
|
||||
const buf = makeBuf([1, 2, 3]);
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'bin-sha', content: buf });
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(true);
|
||||
|
|
@ -143,6 +96,7 @@ describe('SyncManager – binary file handling', () => {
|
|||
});
|
||||
|
||||
it('updates metadata after pulling binary file', async () => {
|
||||
const { manager, mockAdapter, mockGitService, mockSettings } = mocks;
|
||||
const buf = makeBuf([0, 1, 2]);
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'bin-sha', content: buf });
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(false);
|
||||
|
|
|
|||
|
|
@ -1,71 +1,21 @@
|
|||
/* 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';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { createSyncManagerMocks, SyncManagerMocks } from './sync-manager-test-helpers';
|
||||
|
||||
vi.mock('obsidian');
|
||||
|
||||
describe('SyncManager – hidden file support', () => {
|
||||
let manager: SyncManager;
|
||||
let mockApp: Mocked<App>;
|
||||
let mockGitService: Mocked<GitServiceInterface>;
|
||||
let mockAdapter: Mocked<DataAdapter>;
|
||||
let mockSettings: GitLabFilesPushSettings;
|
||||
let mocks: SyncManagerMocks;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockAdapter = {
|
||||
exists: vi.fn(),
|
||||
read: vi.fn(),
|
||||
write: vi.fn(),
|
||||
writeBinary: vi.fn(),
|
||||
mkdir: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as Mocked<DataAdapter>;
|
||||
|
||||
mockApp = {
|
||||
vault: {
|
||||
read: vi.fn(),
|
||||
modify: vi.fn(),
|
||||
getFileByPath: vi.fn().mockReturnValue(null),
|
||||
adapter: mockAdapter,
|
||||
},
|
||||
} 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: {},
|
||||
vaultFolder: '',
|
||||
rootPath: '',
|
||||
} as unknown as GitLabFilesPushSettings;
|
||||
|
||||
manager = new SyncManager(mockApp, mockGitService, mockSettings);
|
||||
// @ts-ignore - accessing private for test
|
||||
manager.saveSettings = vi.fn().mockResolvedValue(undefined);
|
||||
mocks = createSyncManagerMocks();
|
||||
});
|
||||
|
||||
describe('pullFile with hidden paths', () => {
|
||||
it('creates single hidden parent directory on pull', async () => {
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({
|
||||
sha: 'abc123',
|
||||
content: '{"key":"value"}',
|
||||
});
|
||||
const { manager, mockAdapter, mockGitService } = mocks;
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'abc123', content: '{"key":"value"}' });
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(false);
|
||||
|
||||
await manager.pullFile('.claude/settings.json');
|
||||
|
|
@ -75,10 +25,8 @@ describe('SyncManager – hidden file support', () => {
|
|||
});
|
||||
|
||||
it('creates all nested hidden parent directories on pull', async () => {
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({
|
||||
sha: 'def456',
|
||||
content: 'nested content',
|
||||
});
|
||||
const { manager, mockAdapter, mockGitService } = mocks;
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'def456', content: 'nested content' });
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(false);
|
||||
|
||||
await manager.pullFile('.claude/memory/user.md');
|
||||
|
|
@ -89,10 +37,8 @@ describe('SyncManager – hidden file support', () => {
|
|||
});
|
||||
|
||||
it('does not fail if hidden directory already exists (mkdir throws)', async () => {
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({
|
||||
sha: 'abc123',
|
||||
content: 'content',
|
||||
});
|
||||
const { manager, mockAdapter, mockGitService } = mocks;
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'abc123', content: 'content' });
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(false);
|
||||
vi.mocked(mockAdapter.mkdir).mockRejectedValue(new Error('already exists'));
|
||||
|
||||
|
|
@ -101,10 +47,8 @@ describe('SyncManager – hidden file support', () => {
|
|||
});
|
||||
|
||||
it('updates metadata after pulling hidden file', async () => {
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({
|
||||
sha: 'sha-hidden',
|
||||
content: 'file content',
|
||||
});
|
||||
const { manager, mockAdapter, mockGitService, mockSettings } = mocks;
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'sha-hidden', content: 'file content' });
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(false);
|
||||
|
||||
await manager.pullFile('.claude/CLAUDE.md');
|
||||
|
|
@ -117,6 +61,7 @@ describe('SyncManager – hidden file support', () => {
|
|||
|
||||
describe('pushFile with hidden paths', () => {
|
||||
it('pushes hidden file content via string path', async () => {
|
||||
const { manager, mockAdapter, mockGitService } = mocks;
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(true);
|
||||
vi.mocked(mockAdapter.read).mockResolvedValue('# Memory\n\nsome content');
|
||||
vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: '', content: '' });
|
||||
|
|
@ -125,15 +70,12 @@ describe('SyncManager – hidden file support', () => {
|
|||
await manager.pushFile('.claude/CLAUDE.md');
|
||||
|
||||
expect(mockGitService.pushFile).toHaveBeenCalledWith(
|
||||
'.claude/CLAUDE.md',
|
||||
'# Memory\n\nsome content',
|
||||
'main',
|
||||
expect.any(String),
|
||||
''
|
||||
'.claude/CLAUDE.md', '# Memory\n\nsome content', 'main', expect.any(String), ''
|
||||
);
|
||||
});
|
||||
|
||||
it('skips push when hidden file is already in sync', async () => {
|
||||
const { manager, mockAdapter, mockGitService } = mocks;
|
||||
const content = 'same content';
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(true);
|
||||
vi.mocked(mockAdapter.read).mockResolvedValue(content);
|
||||
|
|
@ -144,7 +86,8 @@ describe('SyncManager – hidden file support', () => {
|
|||
expect(mockGitService.pushFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not exist notice when hidden file is missing', async () => {
|
||||
it('does not push when hidden file is missing from vault', async () => {
|
||||
const { manager, mockAdapter, mockGitService } = mocks;
|
||||
vi.mocked(mockAdapter.exists).mockResolvedValue(false);
|
||||
|
||||
await manager.pushFile('.claude/missing.json');
|
||||
|
|
|
|||
64
tests/logic/sync-manager-test-helpers.ts
Normal file
64
tests/logic/sync-manager-test-helpers.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { vi, 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';
|
||||
|
||||
export interface SyncManagerMocks {
|
||||
manager: SyncManager;
|
||||
mockApp: Mocked<App>;
|
||||
mockGitService: Mocked<GitServiceInterface>;
|
||||
mockAdapter: Mocked<DataAdapter>;
|
||||
mockSettings: GitLabFilesPushSettings;
|
||||
}
|
||||
|
||||
export function createSyncManagerMocks(): SyncManagerMocks {
|
||||
const mockAdapter = {
|
||||
exists: vi.fn(),
|
||||
read: vi.fn(),
|
||||
write: vi.fn(),
|
||||
readBinary: vi.fn(),
|
||||
writeBinary: vi.fn(),
|
||||
mkdir: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as Mocked<DataAdapter>;
|
||||
|
||||
const mockApp = {
|
||||
vault: {
|
||||
read: vi.fn(),
|
||||
readBinary: vi.fn(),
|
||||
modify: vi.fn(),
|
||||
modifyBinary: vi.fn(),
|
||||
getFileByPath: vi.fn().mockReturnValue(null),
|
||||
adapter: mockAdapter,
|
||||
},
|
||||
} as unknown as Mocked<App>;
|
||||
|
||||
const 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>;
|
||||
|
||||
const mockSettings = {
|
||||
serviceType: 'github',
|
||||
githubToken: 'token',
|
||||
githubOwner: 'owner',
|
||||
githubRepo: 'repo',
|
||||
branch: 'main',
|
||||
syncMetadata: {},
|
||||
vaultFolder: '',
|
||||
rootPath: '',
|
||||
} as unknown as GitLabFilesPushSettings;
|
||||
|
||||
const manager = new SyncManager(mockApp, mockGitService, mockSettings);
|
||||
// @ts-ignore - accessing private for test
|
||||
manager.saveSettings = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
return { manager, mockApp, mockGitService, mockAdapter, mockSettings };
|
||||
}
|
||||
|
||||
export const makeBuf = (bytes: number[]) => new Uint8Array(bytes).buffer;
|
||||
Loading…
Reference in a new issue