mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 05:23:08 +00:00
Fixed 29 TypeScript compilation errors in test files: **breaking-change-detector.test.ts** (22 errors): - Added missing `nodeType`, `fromVersion`, `toVersion` to BreakingChange objects - All 22 BreakingChange object instantiations now comply with interface **node-migration-service.test.ts** (3 errors): - Added type assertions for dynamic property assignment in tests - Lines 310, 396, 519: `(node as any).property = value` **workflow-versioning-service.test.ts** (5 errors): - Fixed N8nApiClient constructor: takes config object, not separate params - Fixed updateWorkflow mock: returns Workflow object, not undefined All tests now compile successfully with `npm run typecheck`. Conceived by Romuald Członkowski - www.aiadvisors.pl/en 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
617 lines
24 KiB
TypeScript
617 lines
24 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { WorkflowVersioningService, type WorkflowVersion, type BackupResult } from '@/services/workflow-versioning-service';
|
|
import { NodeRepository } from '@/database/node-repository';
|
|
import { N8nApiClient } from '@/services/n8n-api-client';
|
|
import { WorkflowValidator } from '@/services/workflow-validator';
|
|
import type { Workflow } from '@/types/n8n-api';
|
|
|
|
vi.mock('@/database/node-repository');
|
|
vi.mock('@/services/n8n-api-client');
|
|
vi.mock('@/services/workflow-validator');
|
|
|
|
describe('WorkflowVersioningService', () => {
|
|
let service: WorkflowVersioningService;
|
|
let mockRepository: NodeRepository;
|
|
let mockApiClient: N8nApiClient;
|
|
|
|
const createMockWorkflow = (id: string, name: string, nodes: any[] = []): Workflow => ({
|
|
id,
|
|
name,
|
|
active: false,
|
|
nodes,
|
|
connections: {},
|
|
settings: {},
|
|
createdAt: '2025-01-01T00:00:00.000Z',
|
|
updatedAt: '2025-01-01T00:00:00.000Z'
|
|
});
|
|
|
|
const createMockVersion = (versionNumber: number): WorkflowVersion => ({
|
|
id: versionNumber,
|
|
workflowId: 'workflow-1',
|
|
versionNumber,
|
|
workflowName: 'Test Workflow',
|
|
workflowSnapshot: createMockWorkflow('workflow-1', 'Test Workflow'),
|
|
trigger: 'partial_update',
|
|
createdAt: '2025-01-01T00:00:00.000Z'
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockRepository = new NodeRepository({} as any);
|
|
mockApiClient = new N8nApiClient({ baseUrl: 'http://test', apiKey: 'test-key' });
|
|
service = new WorkflowVersioningService(mockRepository, mockApiClient);
|
|
});
|
|
|
|
describe('createBackup', () => {
|
|
it('should create a backup with version 1 for new workflow', async () => {
|
|
const workflow = createMockWorkflow('workflow-1', 'Test Workflow');
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([]);
|
|
vi.spyOn(mockRepository, 'createWorkflowVersion').mockReturnValue(1);
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(0);
|
|
|
|
const result = await service.createBackup('workflow-1', workflow, {
|
|
trigger: 'partial_update'
|
|
});
|
|
|
|
expect(result.versionId).toBe(1);
|
|
expect(result.versionNumber).toBe(1);
|
|
expect(result.pruned).toBe(0);
|
|
expect(result.message).toContain('Backup created (version 1)');
|
|
});
|
|
|
|
it('should increment version number from latest version', async () => {
|
|
const workflow = createMockWorkflow('workflow-1', 'Test Workflow');
|
|
const existingVersions = [createMockVersion(3), createMockVersion(2)];
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue(existingVersions);
|
|
vi.spyOn(mockRepository, 'createWorkflowVersion').mockReturnValue(4);
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(0);
|
|
|
|
const result = await service.createBackup('workflow-1', workflow, {
|
|
trigger: 'full_update'
|
|
});
|
|
|
|
expect(result.versionNumber).toBe(4);
|
|
expect(mockRepository.createWorkflowVersion).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
versionNumber: 4
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should include context in version metadata', async () => {
|
|
const workflow = createMockWorkflow('workflow-1', 'Test Workflow');
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([]);
|
|
vi.spyOn(mockRepository, 'createWorkflowVersion').mockReturnValue(1);
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(0);
|
|
|
|
await service.createBackup('workflow-1', workflow, {
|
|
trigger: 'autofix',
|
|
operations: [{ type: 'updateNode', nodeId: 'node-1' }],
|
|
fixTypes: ['expression-format'],
|
|
metadata: { testKey: 'testValue' }
|
|
});
|
|
|
|
expect(mockRepository.createWorkflowVersion).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
trigger: 'autofix',
|
|
operations: [{ type: 'updateNode', nodeId: 'node-1' }],
|
|
fixTypes: ['expression-format'],
|
|
metadata: { testKey: 'testValue' }
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should auto-prune to 10 versions and report pruned count', async () => {
|
|
const workflow = createMockWorkflow('workflow-1', 'Test Workflow');
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([createMockVersion(1)]);
|
|
vi.spyOn(mockRepository, 'createWorkflowVersion').mockReturnValue(2);
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(3);
|
|
|
|
const result = await service.createBackup('workflow-1', workflow, {
|
|
trigger: 'partial_update'
|
|
});
|
|
|
|
expect(mockRepository.pruneWorkflowVersions).toHaveBeenCalledWith('workflow-1', 10);
|
|
expect(result.pruned).toBe(3);
|
|
expect(result.message).toContain('pruned 3 old version(s)');
|
|
});
|
|
});
|
|
|
|
describe('getVersionHistory', () => {
|
|
it('should return formatted version history', async () => {
|
|
const versions = [
|
|
createMockVersion(3),
|
|
createMockVersion(2),
|
|
createMockVersion(1)
|
|
];
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue(versions);
|
|
|
|
const result = await service.getVersionHistory('workflow-1', 10);
|
|
|
|
expect(result).toHaveLength(3);
|
|
expect(result[0].versionNumber).toBe(3);
|
|
expect(result[0].workflowId).toBe('workflow-1');
|
|
expect(result[0].size).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should include operation count when operations exist', async () => {
|
|
const versionWithOps: WorkflowVersion = {
|
|
...createMockVersion(1),
|
|
operations: [{ type: 'updateNode' }, { type: 'addNode' }]
|
|
};
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([versionWithOps]);
|
|
|
|
const result = await service.getVersionHistory('workflow-1', 10);
|
|
|
|
expect(result[0].operationCount).toBe(2);
|
|
});
|
|
|
|
it('should include fixTypes when present', async () => {
|
|
const versionWithFixes: WorkflowVersion = {
|
|
...createMockVersion(1),
|
|
fixTypes: ['expression-format', 'typeversion-correction']
|
|
};
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([versionWithFixes]);
|
|
|
|
const result = await service.getVersionHistory('workflow-1', 10);
|
|
|
|
expect(result[0].fixTypesApplied).toEqual(['expression-format', 'typeversion-correction']);
|
|
});
|
|
|
|
it('should respect the limit parameter', async () => {
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([]);
|
|
|
|
await service.getVersionHistory('workflow-1', 5);
|
|
|
|
expect(mockRepository.getWorkflowVersions).toHaveBeenCalledWith('workflow-1', 5);
|
|
});
|
|
});
|
|
|
|
describe('getVersion', () => {
|
|
it('should return the requested version', async () => {
|
|
const version = createMockVersion(1);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(version);
|
|
|
|
const result = await service.getVersion(1);
|
|
|
|
expect(result).toEqual(version);
|
|
});
|
|
|
|
it('should return null if version does not exist', async () => {
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(null);
|
|
|
|
const result = await service.getVersion(999);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('restoreVersion', () => {
|
|
it('should fail if API client is not configured', async () => {
|
|
const serviceWithoutApi = new WorkflowVersioningService(mockRepository);
|
|
|
|
const result = await serviceWithoutApi.restoreVersion('workflow-1', 1);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toContain('API client not configured');
|
|
expect(result.backupCreated).toBe(false);
|
|
});
|
|
|
|
it('should fail if version does not exist', async () => {
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(null);
|
|
|
|
const result = await service.restoreVersion('workflow-1', 999);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toContain('Version 999 not found');
|
|
expect(result.backupCreated).toBe(false);
|
|
});
|
|
|
|
it('should restore latest version when no versionId provided', async () => {
|
|
const version = createMockVersion(3);
|
|
vi.spyOn(mockRepository, 'getLatestWorkflowVersion').mockReturnValue(version);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([]);
|
|
vi.spyOn(mockRepository, 'createWorkflowVersion').mockReturnValue(4);
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(0);
|
|
vi.spyOn(mockApiClient, 'getWorkflow').mockResolvedValue(createMockWorkflow('workflow-1', 'Current'));
|
|
vi.spyOn(mockApiClient, 'updateWorkflow').mockResolvedValue(createMockWorkflow('workflow-1', 'Restored'));
|
|
|
|
const result = await service.restoreVersion('workflow-1', undefined, false);
|
|
|
|
expect(mockRepository.getLatestWorkflowVersion).toHaveBeenCalledWith('workflow-1');
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should fail if no backup versions exist and no versionId provided', async () => {
|
|
vi.spyOn(mockRepository, 'getLatestWorkflowVersion').mockReturnValue(null);
|
|
|
|
const result = await service.restoreVersion('workflow-1', undefined);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toContain('No backup versions found');
|
|
});
|
|
|
|
it('should validate version before restore when validateBefore is true', async () => {
|
|
const version = createMockVersion(1);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(version);
|
|
|
|
const mockValidator = {
|
|
validateWorkflow: vi.fn().mockResolvedValue({
|
|
errors: [{ message: 'Validation error' }]
|
|
})
|
|
};
|
|
vi.spyOn(WorkflowValidator.prototype, 'validateWorkflow').mockImplementation(
|
|
mockValidator.validateWorkflow
|
|
);
|
|
|
|
const result = await service.restoreVersion('workflow-1', 1, true);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toContain('has validation errors');
|
|
expect(result.validationErrors).toEqual(['Validation error']);
|
|
expect(result.backupCreated).toBe(false);
|
|
});
|
|
|
|
it('should skip validation when validateBefore is false', async () => {
|
|
const version = createMockVersion(1);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(version);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([]);
|
|
vi.spyOn(mockRepository, 'createWorkflowVersion').mockReturnValue(2);
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(0);
|
|
vi.spyOn(mockApiClient, 'getWorkflow').mockResolvedValue(createMockWorkflow('workflow-1', 'Current'));
|
|
vi.spyOn(mockApiClient, 'updateWorkflow').mockResolvedValue(createMockWorkflow('workflow-1', 'Restored'));
|
|
|
|
const mockValidator = vi.fn();
|
|
vi.spyOn(WorkflowValidator.prototype, 'validateWorkflow').mockImplementation(mockValidator);
|
|
|
|
await service.restoreVersion('workflow-1', 1, false);
|
|
|
|
expect(mockValidator).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should create backup before restoring', async () => {
|
|
const versionToRestore = createMockVersion(1);
|
|
const currentWorkflow = createMockWorkflow('workflow-1', 'Current Workflow');
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(versionToRestore);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([createMockVersion(2)]);
|
|
vi.spyOn(mockRepository, 'createWorkflowVersion').mockReturnValue(3);
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(0);
|
|
vi.spyOn(mockApiClient, 'getWorkflow').mockResolvedValue(currentWorkflow);
|
|
vi.spyOn(mockApiClient, 'updateWorkflow').mockResolvedValue(createMockWorkflow('workflow-1', 'Restored'));
|
|
|
|
const result = await service.restoreVersion('workflow-1', 1, false);
|
|
|
|
expect(mockApiClient.getWorkflow).toHaveBeenCalledWith('workflow-1');
|
|
expect(mockRepository.createWorkflowVersion).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workflowSnapshot: currentWorkflow,
|
|
metadata: expect.objectContaining({
|
|
reason: 'Backup before rollback',
|
|
restoringToVersion: 1
|
|
})
|
|
})
|
|
);
|
|
expect(result.backupCreated).toBe(true);
|
|
expect(result.backupVersionId).toBe(3);
|
|
});
|
|
|
|
it('should fail if backup creation fails', async () => {
|
|
const version = createMockVersion(1);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(version);
|
|
vi.spyOn(mockApiClient, 'getWorkflow').mockRejectedValue(new Error('Backup failed'));
|
|
|
|
const result = await service.restoreVersion('workflow-1', 1, false);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toContain('Failed to create backup before restore');
|
|
expect(result.backupCreated).toBe(false);
|
|
});
|
|
|
|
it('should successfully restore workflow', async () => {
|
|
const versionToRestore = createMockVersion(1);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(versionToRestore);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([createMockVersion(2)]);
|
|
vi.spyOn(mockRepository, 'createWorkflowVersion').mockReturnValue(3);
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(0);
|
|
vi.spyOn(mockApiClient, 'getWorkflow').mockResolvedValue(createMockWorkflow('workflow-1', 'Current'));
|
|
vi.spyOn(mockApiClient, 'updateWorkflow').mockResolvedValue(createMockWorkflow('workflow-1', 'Restored'));
|
|
|
|
const result = await service.restoreVersion('workflow-1', 1, false);
|
|
|
|
expect(mockApiClient.updateWorkflow).toHaveBeenCalledWith('workflow-1', versionToRestore.workflowSnapshot);
|
|
expect(result.success).toBe(true);
|
|
expect(result.message).toContain('Successfully restored workflow to version 1');
|
|
expect(result.fromVersion).toBe(3);
|
|
expect(result.toVersionId).toBe(1);
|
|
});
|
|
|
|
it('should handle restore API failures', async () => {
|
|
const version = createMockVersion(1);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(version);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersions').mockReturnValue([]);
|
|
vi.spyOn(mockRepository, 'createWorkflowVersion').mockReturnValue(2);
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(0);
|
|
vi.spyOn(mockApiClient, 'getWorkflow').mockResolvedValue(createMockWorkflow('workflow-1', 'Current'));
|
|
vi.spyOn(mockApiClient, 'updateWorkflow').mockRejectedValue(new Error('API Error'));
|
|
|
|
const result = await service.restoreVersion('workflow-1', 1, false);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toContain('Failed to restore workflow');
|
|
expect(result.backupCreated).toBe(true);
|
|
expect(result.backupVersionId).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('deleteVersion', () => {
|
|
it('should delete a specific version', async () => {
|
|
const version = createMockVersion(1);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(version);
|
|
vi.spyOn(mockRepository, 'deleteWorkflowVersion').mockReturnValue(undefined);
|
|
|
|
const result = await service.deleteVersion(1);
|
|
|
|
expect(mockRepository.deleteWorkflowVersion).toHaveBeenCalledWith(1);
|
|
expect(result.success).toBe(true);
|
|
expect(result.message).toContain('Deleted version 1');
|
|
});
|
|
|
|
it('should fail if version does not exist', async () => {
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(null);
|
|
|
|
const result = await service.deleteVersion(999);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toContain('Version 999 not found');
|
|
});
|
|
});
|
|
|
|
describe('deleteAllVersions', () => {
|
|
it('should delete all versions for a workflow', async () => {
|
|
vi.spyOn(mockRepository, 'getWorkflowVersionCount').mockReturnValue(5);
|
|
vi.spyOn(mockRepository, 'deleteWorkflowVersionsByWorkflowId').mockReturnValue(5);
|
|
|
|
const result = await service.deleteAllVersions('workflow-1');
|
|
|
|
expect(result.deleted).toBe(5);
|
|
expect(result.message).toContain('Deleted 5 version(s)');
|
|
});
|
|
|
|
it('should return zero if no versions exist', async () => {
|
|
vi.spyOn(mockRepository, 'getWorkflowVersionCount').mockReturnValue(0);
|
|
|
|
const result = await service.deleteAllVersions('workflow-1');
|
|
|
|
expect(result.deleted).toBe(0);
|
|
expect(result.message).toContain('No versions found');
|
|
});
|
|
});
|
|
|
|
describe('pruneVersions', () => {
|
|
it('should prune versions and return counts', async () => {
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(3);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersionCount').mockReturnValue(10);
|
|
|
|
const result = await service.pruneVersions('workflow-1', 10);
|
|
|
|
expect(result.pruned).toBe(3);
|
|
expect(result.remaining).toBe(10);
|
|
});
|
|
|
|
it('should use custom maxVersions parameter', async () => {
|
|
vi.spyOn(mockRepository, 'pruneWorkflowVersions').mockReturnValue(0);
|
|
vi.spyOn(mockRepository, 'getWorkflowVersionCount').mockReturnValue(5);
|
|
|
|
await service.pruneVersions('workflow-1', 5);
|
|
|
|
expect(mockRepository.pruneWorkflowVersions).toHaveBeenCalledWith('workflow-1', 5);
|
|
});
|
|
});
|
|
|
|
describe('truncateAllVersions', () => {
|
|
it('should refuse to truncate without confirmation', async () => {
|
|
const result = await service.truncateAllVersions(false);
|
|
|
|
expect(result.deleted).toBe(0);
|
|
expect(result.message).toContain('not confirmed');
|
|
});
|
|
|
|
it('should truncate all versions when confirmed', async () => {
|
|
vi.spyOn(mockRepository, 'truncateWorkflowVersions').mockReturnValue(50);
|
|
|
|
const result = await service.truncateAllVersions(true);
|
|
|
|
expect(result.deleted).toBe(50);
|
|
expect(result.message).toContain('Truncated workflow_versions table');
|
|
});
|
|
});
|
|
|
|
describe('getStorageStats', () => {
|
|
it('should return formatted storage statistics', async () => {
|
|
const mockStats = {
|
|
totalVersions: 10,
|
|
totalSize: 1024000,
|
|
byWorkflow: [
|
|
{
|
|
workflowId: 'workflow-1',
|
|
workflowName: 'Test Workflow',
|
|
versionCount: 5,
|
|
totalSize: 512000,
|
|
lastBackup: '2025-01-01T00:00:00.000Z'
|
|
}
|
|
]
|
|
};
|
|
|
|
vi.spyOn(mockRepository, 'getVersionStorageStats').mockReturnValue(mockStats);
|
|
|
|
const result = await service.getStorageStats();
|
|
|
|
expect(result.totalVersions).toBe(10);
|
|
expect(result.totalSizeFormatted).toContain('KB');
|
|
expect(result.byWorkflow).toHaveLength(1);
|
|
expect(result.byWorkflow[0].totalSizeFormatted).toContain('KB');
|
|
});
|
|
|
|
it('should format bytes correctly', async () => {
|
|
const mockStats = {
|
|
totalVersions: 1,
|
|
totalSize: 0,
|
|
byWorkflow: []
|
|
};
|
|
|
|
vi.spyOn(mockRepository, 'getVersionStorageStats').mockReturnValue(mockStats);
|
|
|
|
const result = await service.getStorageStats();
|
|
|
|
expect(result.totalSizeFormatted).toBe('0 Bytes');
|
|
});
|
|
});
|
|
|
|
describe('compareVersions', () => {
|
|
it('should detect added nodes', async () => {
|
|
const v1 = createMockVersion(1);
|
|
v1.workflowSnapshot.nodes = [{ id: 'node-1', name: 'Node 1', type: 'test', typeVersion: 1, position: [0, 0], parameters: {} }];
|
|
|
|
const v2 = createMockVersion(2);
|
|
v2.workflowSnapshot.nodes = [
|
|
{ id: 'node-1', name: 'Node 1', type: 'test', typeVersion: 1, position: [0, 0], parameters: {} },
|
|
{ id: 'node-2', name: 'Node 2', type: 'test', typeVersion: 1, position: [100, 0], parameters: {} }
|
|
];
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion')
|
|
.mockReturnValueOnce(v1)
|
|
.mockReturnValueOnce(v2);
|
|
|
|
const result = await service.compareVersions(1, 2);
|
|
|
|
expect(result.addedNodes).toEqual(['node-2']);
|
|
expect(result.removedNodes).toEqual([]);
|
|
expect(result.modifiedNodes).toEqual([]);
|
|
});
|
|
|
|
it('should detect removed nodes', async () => {
|
|
const v1 = createMockVersion(1);
|
|
v1.workflowSnapshot.nodes = [
|
|
{ id: 'node-1', name: 'Node 1', type: 'test', typeVersion: 1, position: [0, 0], parameters: {} },
|
|
{ id: 'node-2', name: 'Node 2', type: 'test', typeVersion: 1, position: [100, 0], parameters: {} }
|
|
];
|
|
|
|
const v2 = createMockVersion(2);
|
|
v2.workflowSnapshot.nodes = [{ id: 'node-1', name: 'Node 1', type: 'test', typeVersion: 1, position: [0, 0], parameters: {} }];
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion')
|
|
.mockReturnValueOnce(v1)
|
|
.mockReturnValueOnce(v2);
|
|
|
|
const result = await service.compareVersions(1, 2);
|
|
|
|
expect(result.removedNodes).toEqual(['node-2']);
|
|
expect(result.addedNodes).toEqual([]);
|
|
});
|
|
|
|
it('should detect modified nodes', async () => {
|
|
const v1 = createMockVersion(1);
|
|
v1.workflowSnapshot.nodes = [{ id: 'node-1', name: 'Node 1', type: 'test', typeVersion: 1, position: [0, 0], parameters: {} }];
|
|
|
|
const v2 = createMockVersion(2);
|
|
v2.workflowSnapshot.nodes = [{ id: 'node-1', name: 'Node 1', type: 'test', typeVersion: 2, position: [0, 0], parameters: {} }];
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion')
|
|
.mockReturnValueOnce(v1)
|
|
.mockReturnValueOnce(v2);
|
|
|
|
const result = await service.compareVersions(1, 2);
|
|
|
|
expect(result.modifiedNodes).toEqual(['node-1']);
|
|
});
|
|
|
|
it('should detect connection changes', async () => {
|
|
const v1 = createMockVersion(1);
|
|
v1.workflowSnapshot.connections = { 'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] } };
|
|
|
|
const v2 = createMockVersion(2);
|
|
v2.workflowSnapshot.connections = {};
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion')
|
|
.mockReturnValueOnce(v1)
|
|
.mockReturnValueOnce(v2);
|
|
|
|
const result = await service.compareVersions(1, 2);
|
|
|
|
expect(result.connectionChanges).toBe(1);
|
|
});
|
|
|
|
it('should detect settings changes', async () => {
|
|
const v1 = createMockVersion(1);
|
|
v1.workflowSnapshot.settings = { executionOrder: 'v0' };
|
|
|
|
const v2 = createMockVersion(2);
|
|
v2.workflowSnapshot.settings = { executionOrder: 'v1' };
|
|
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion')
|
|
.mockReturnValueOnce(v1)
|
|
.mockReturnValueOnce(v2);
|
|
|
|
const result = await service.compareVersions(1, 2);
|
|
|
|
expect(result.settingChanges).toHaveProperty('executionOrder');
|
|
expect(result.settingChanges.executionOrder.before).toBe('v0');
|
|
expect(result.settingChanges.executionOrder.after).toBe('v1');
|
|
});
|
|
|
|
it('should throw error if version not found', async () => {
|
|
vi.spyOn(mockRepository, 'getWorkflowVersion').mockReturnValue(null);
|
|
|
|
await expect(service.compareVersions(1, 2)).rejects.toThrow('One or both versions not found');
|
|
});
|
|
});
|
|
|
|
describe('formatBytes', () => {
|
|
it('should format bytes to human-readable string', () => {
|
|
// Access private method through any cast
|
|
const formatBytes = (service as any).formatBytes.bind(service);
|
|
|
|
expect(formatBytes(0)).toBe('0 Bytes');
|
|
expect(formatBytes(500)).toBe('500 Bytes');
|
|
expect(formatBytes(1024)).toBe('1 KB');
|
|
expect(formatBytes(1048576)).toBe('1 MB');
|
|
expect(formatBytes(1073741824)).toBe('1 GB');
|
|
});
|
|
});
|
|
|
|
describe('diffObjects', () => {
|
|
it('should detect object differences', () => {
|
|
const diffObjects = (service as any).diffObjects.bind(service);
|
|
|
|
const obj1 = { a: 1, b: 2 };
|
|
const obj2 = { a: 1, b: 3, c: 4 };
|
|
|
|
const diff = diffObjects(obj1, obj2);
|
|
|
|
expect(diff).toHaveProperty('b');
|
|
expect(diff.b).toEqual({ before: 2, after: 3 });
|
|
expect(diff).toHaveProperty('c');
|
|
expect(diff.c).toEqual({ before: undefined, after: 4 });
|
|
});
|
|
|
|
it('should return empty object when no differences', () => {
|
|
const diffObjects = (service as any).diffObjects.bind(service);
|
|
|
|
const obj1 = { a: 1, b: 2 };
|
|
const obj2 = { a: 1, b: 2 };
|
|
|
|
const diff = diffObjects(obj1, obj2);
|
|
|
|
expect(Object.keys(diff)).toHaveLength(0);
|
|
});
|
|
});
|
|
});
|