Skip to main content
Glama
workflow-operations.test.ts11.6 kB
/** * Unit tests for WorkflowOperations */ import { WorkflowOperations } from '../../operations/workflow-operations.js'; import { AxiosInstance } from 'axios'; import { ILogger, AEMConfig } from '../../interfaces/index.js'; // Mock dependencies const mockHttpClient: jest.Mocked<AxiosInstance> = { get: jest.fn(), post: jest.fn(), put: jest.fn(), delete: jest.fn(), patch: jest.fn(), request: jest.fn(), head: jest.fn(), options: jest.fn(), defaults: {} as any, interceptors: {} as any, } as any; const mockLogger: jest.Mocked<ILogger> = { error: jest.fn(), warn: jest.fn(), info: jest.fn(), debug: jest.fn(), trace: jest.fn(), methodStart: jest.fn(), methodEnd: jest.fn(), methodError: jest.fn(), httpRequest: jest.fn(), aemOperation: jest.fn(), performance: jest.fn(), } as any; const mockAEMConfig: AEMConfig = { host: 'http://localhost:4502', author: 'http://localhost:4502', publish: 'http://localhost:4503', serviceUser: { username: 'admin', password: 'admin', }, endpoints: { content: '/content', dam: '/content/dam', query: '/bin/querybuilder.json', crxde: '/crx/de', jcr: '', replicate: '/bin/replicate.json', wcmcommand: '/bin/wcmcommand', }, contentPaths: { sitesRoot: '/content', assetsRoot: '/content/dam', templatesRoot: '/conf', experienceFragmentsRoot: '/content/experience-fragments', }, replication: { publisherUrls: ['http://localhost:4503'], defaultReplicationAgent: 'publish', }, components: { allowedTypes: ['text', 'image', 'teaser'], defaultProperties: {}, }, queries: { maxLimit: 1000, defaultLimit: 20, timeoutMs: 30000, }, validation: { maxDepth: 10, allowedLocales: ['en', 'en_us', 'en_gb'], }, }; describe('WorkflowOperations', () => { let workflowOps: WorkflowOperations; beforeEach(() => { workflowOps = new WorkflowOperations(mockHttpClient, mockLogger, mockAEMConfig); jest.clearAllMocks(); }); describe('startWorkflow', () => { it('should start a workflow successfully', async () => { const mockResponse = { data: { workflowId: 'workflow-123', status: 'RUNNING', model: '/var/workflow/models/test-model', payloadPath: '/content/test', }, }; mockHttpClient.post.mockResolvedValueOnce(mockResponse); const request = { model: '/var/workflow/models/test-model', payloadPath: '/content/test', title: 'Test Workflow', comment: 'Starting test workflow', }; const result = await workflowOps.startWorkflow(request); expect(result.success).toBe(true); expect(result.operation).toBe('startWorkflow'); expect(result.data.workflowId).toBe('workflow-123'); expect(result.data.status).toBe('RUNNING'); expect(mockHttpClient.post).toHaveBeenCalledWith( '/bin/workflow/instances', expect.any(URLSearchParams), expect.objectContaining({ headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }) ); }); it('should handle workflow start failure', async () => { mockHttpClient.post.mockRejectedValueOnce(new Error('Workflow start failed')); const request = { model: '/var/workflow/models/test-model', payloadPath: '/content/test', }; await expect(workflowOps.startWorkflow(request)).rejects.toThrow(); }); it('should validate required parameters', async () => { const request = { payloadPath: '/content/test', // Missing required 'model' parameter } as any; await expect(workflowOps.startWorkflow(request)).rejects.toThrow(); }); }); describe('getWorkflowStatus', () => { it('should get workflow status successfully', async () => { const mockResponse = { data: { workflowId: 'workflow-123', status: 'RUNNING', currentStep: 'Review', started: '2024-01-01T10:00:00Z', model: '/var/workflow/models/test-model', payloadPath: '/content/test', history: [ { step: 'Start', status: 'COMPLETED', timestamp: '2024-01-01T10:00:00Z', }, { step: 'Review', status: 'RUNNING', timestamp: '2024-01-01T10:05:00Z', }, ], }, }; mockHttpClient.get.mockResolvedValueOnce(mockResponse); const result = await workflowOps.getWorkflowStatus('workflow-123'); expect(result.success).toBe(true); expect(result.operation).toBe('getWorkflowStatus'); expect(result.data.workflowId).toBe('workflow-123'); expect(result.data.status).toBe('RUNNING'); expect(mockHttpClient.get).toHaveBeenCalledWith('/bin/workflow/instances/workflow-123.json'); }); it('should handle workflow not found', async () => { const mockError = { response: { status: 404, data: { message: 'Workflow not found' }, }, }; mockHttpClient.get.mockRejectedValueOnce(mockError); await expect(workflowOps.getWorkflowStatus('nonexistent-workflow')).rejects.toThrow(); }); }); describe('completeWorkflowStep', () => { it('should complete workflow step successfully', async () => { const mockResponse = { data: { workflowId: 'workflow-123', completedStep: 'Review', nextStep: 'Approve', status: 'RUNNING', }, }; mockHttpClient.post.mockResolvedValueOnce(mockResponse); const result = await workflowOps.completeWorkflowStep('workflow-123', 'Review', 'Step completed'); expect(result.success).toBe(true); expect(result.operation).toBe('completeWorkflowStep'); expect(result.data.workflowId).toBe('workflow-123'); expect(result.data.completedStep).toBe('Review'); expect(mockHttpClient.post).toHaveBeenCalledWith( '/bin/workflow/instances/workflow-123/steps/Review', expect.any(URLSearchParams), expect.objectContaining({ headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }) ); }); it('should handle step completion failure', async () => { mockHttpClient.post.mockRejectedValueOnce(new Error('Step completion failed')); await expect(workflowOps.completeWorkflowStep('workflow-123', 'Review')).rejects.toThrow(); }); }); describe('cancelWorkflow', () => { it('should cancel workflow successfully', async () => { const mockResponse = { data: { workflowId: 'workflow-123', status: 'CANCELLED', cancelledAt: '2024-01-01T11:00:00Z', }, }; mockHttpClient.post.mockResolvedValueOnce(mockResponse); const result = await workflowOps.cancelWorkflow('workflow-123', 'Workflow cancelled by user'); expect(result.success).toBe(true); expect(result.operation).toBe('cancelWorkflow'); expect(result.data.workflowId).toBe('workflow-123'); expect(result.data.status).toBe('CANCELLED'); expect(mockHttpClient.post).toHaveBeenCalledWith( '/bin/workflow/instances/workflow-123/cancel', expect.any(URLSearchParams), expect.objectContaining({ headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }) ); }); }); describe('listActiveWorkflows', () => { it('should list active workflows successfully', async () => { const mockResponse = { data: { workflows: [ { workflowId: 'workflow-123', status: 'RUNNING', model: '/var/workflow/models/test-model', payloadPath: '/content/test', started: '2024-01-01T10:00:00Z', }, { workflowId: 'workflow-456', status: 'RUNNING', model: '/var/workflow/models/approval-model', payloadPath: '/content/page', started: '2024-01-01T09:30:00Z', }, ], totalCount: 2, }, }; mockHttpClient.get.mockResolvedValueOnce(mockResponse); const result = await workflowOps.listActiveWorkflows(10); expect(result.success).toBe(true); expect(result.operation).toBe('listActiveWorkflows'); expect(result.data.workflows).toHaveLength(2); expect(result.data.totalCount).toBe(2); expect(mockHttpClient.get).toHaveBeenCalledWith( '/bin/workflow/instances.json', expect.objectContaining({ params: { limit: 10, status: 'RUNNING' }, }) ); }); it('should use default limit when not specified', async () => { const mockResponse = { data: { workflows: [], totalCount: 0 } }; mockHttpClient.get.mockResolvedValueOnce(mockResponse); await workflowOps.listActiveWorkflows(); expect(mockHttpClient.get).toHaveBeenCalledWith( '/bin/workflow/instances.json', expect.objectContaining({ params: { limit: 20, status: 'RUNNING' }, }) ); }); }); describe('suspendWorkflow', () => { it('should suspend workflow successfully', async () => { const mockResponse = { data: { workflowId: 'workflow-123', status: 'SUSPENDED', suspendedAt: '2024-01-01T11:00:00Z', }, }; mockHttpClient.post.mockResolvedValueOnce(mockResponse); const result = await workflowOps.suspendWorkflow('workflow-123', 'Workflow suspended for review'); expect(result.success).toBe(true); expect(result.operation).toBe('suspendWorkflow'); expect(result.data.workflowId).toBe('workflow-123'); expect(result.data.status).toBe('SUSPENDED'); }); }); describe('resumeWorkflow', () => { it('should resume workflow successfully', async () => { const mockResponse = { data: { workflowId: 'workflow-123', status: 'RUNNING', resumedAt: '2024-01-01T12:00:00Z', }, }; mockHttpClient.post.mockResolvedValueOnce(mockResponse); const result = await workflowOps.resumeWorkflow('workflow-123'); expect(result.success).toBe(true); expect(result.operation).toBe('resumeWorkflow'); expect(result.data.workflowId).toBe('workflow-123'); expect(result.data.status).toBe('RUNNING'); }); }); describe('getWorkflowModels', () => { it('should get workflow models successfully', async () => { const mockResponse = { data: { models: [ { id: '/var/workflow/models/test-model', title: 'Test Workflow Model', description: 'A test workflow model', version: '1.0', }, { id: '/var/workflow/models/approval-model', title: 'Approval Workflow Model', description: 'An approval workflow model', version: '1.2', }, ], }, }; mockHttpClient.get.mockResolvedValueOnce(mockResponse); const result = await workflowOps.getWorkflowModels(); expect(result.success).toBe(true); expect(result.operation).toBe('getWorkflowModels'); expect(result.data.models).toHaveLength(2); expect(mockHttpClient.get).toHaveBeenCalledWith('/bin/workflow/models.json'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/indrasishbanerjee/aem-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server