Skip to main content
Glama
entries.test.js14.7 kB
/** * Entries Endpoint Tests for Gravity MCP * Tests all 6 entries management tools with comprehensive coverage */ import GravityFormsClient from '../gravity-forms-client.js'; import { TestRunner, TestAssert, MockHttpClient, MockResponse, setupTestEnvironment, generateMockEntry, generateFieldFilter, generateSearchParams, generatePagingParams, generateSortingParams } from './helpers.js'; const suite = new TestRunner('Entries Endpoint Tests'); let client; let mockHttpClient; let testEnv; suite.beforeEach(() => { testEnv = setupTestEnvironment(); mockHttpClient = new MockHttpClient(); client = new GravityFormsClient(testEnv); client.httpClient = mockHttpClient; client.allowDelete = true; mockHttpClient.setMockResponse('GET', '/forms', new MockResponse({ forms: [] })); }); // ================================= // LIST ENTRIES TESTS // ================================= suite.test('List Entries: Should list all entries with pagination', async () => { const mockEntries = [ generateMockEntry(1, { id: 101 }), generateMockEntry(1, { id: 102 }) ]; mockHttpClient.setMockResponse('GET', '/entries', new MockResponse({ entries: mockEntries, total_count: 2 })); const result = await client.listEntries(); TestAssert.lengthOf(result.entries, 2); TestAssert.equal(result.entries[0].id, 101); TestAssert.equal(result.total_count, 2); }); suite.test('List Entries: Should filter by form IDs', async () => { const formEntries = [ generateMockEntry(5, { id: 1 }), generateMockEntry(5, { id: 2 }) ]; mockHttpClient.setMockResponse('GET', '/entries', new MockResponse({ entries: formEntries })); const result = await client.listEntries({ form_ids: [5] }); TestAssert.lengthOf(result.entries, 2); TestAssert.equal(result.entries[0].form_id, 5); }); suite.test('List Entries: Should filter by status', async () => { const activeEntries = [ generateMockEntry(1, { id: 1, status: 'active' }) ]; mockHttpClient.setMockResponse('GET', '/entries', new MockResponse({ entries: activeEntries })); const result = await client.listEntries({ status: 'active' }); TestAssert.lengthOf(result.entries, 1); TestAssert.equal(result.entries[0].status, 'active'); }); suite.test('List Entries: Should handle complex search with field filters', async () => { const filters = [ generateFieldFilter('1', 'John', 'CONTAINS'), generateFieldFilter('date_created', '2024-01-01', '>=') ]; const search = generateSearchParams(filters, 'all'); mockHttpClient.setMockResponse('GET', '/entries', new MockResponse({ entries: [generateMockEntry()] })); const result = await client.listEntries({ search }); TestAssert.isNotNull(result.search_criteria); TestAssert.equal(result.search_criteria.mode, 'all'); TestAssert.lengthOf(result.search_criteria.field_filters, 2); }); suite.test('List Entries: Should handle sorting', async () => { const sorting = generateSortingParams('date_created', 'desc'); mockHttpClient.setMockResponse('GET', '/entries', new MockResponse({ entries: [generateMockEntry()] })); const result = await client.listEntries({ sorting }); TestAssert.isNotNull(result.sorting); TestAssert.equal(result.sorting.direction, 'desc'); }); suite.test('List Entries: Should handle paging parameters', async () => { const paging = generatePagingParams(50, 2); mockHttpClient.setMockResponse('GET', '/entries', new MockResponse({ entries: [] })); const result = await client.listEntries({ paging }); const request = mockHttpClient.getRequests()[0]; TestAssert.includes(JSON.stringify(request.config.params), 'page_size'); }); suite.test('List Entries: Should validate search operators', async () => { const invalidSearch = { field_filters: [{ key: '1', value: 'test', operator: 'INVALID_OP' }] }; await TestAssert.throwsAsync( () => client.listEntries({ search: invalidSearch }), 'Invalid operator', 'Should validate search operators' ); }); // ================================= // GET ENTRY TESTS // ================================= suite.test('Get Entry: Should get specific entry by ID', async () => { const mockEntry = generateMockEntry(1, { id: 123 }); mockHttpClient.setMockResponse('GET', '/entries/123', new MockResponse(mockEntry)); const result = await client.getEntry({ id: 123 }); TestAssert.equal(result.entry.id, 123); TestAssert.equal(result.form_id, 1); TestAssert.equal(result.status, 'active'); }); suite.test('Get Entry: Should handle entry with file uploads', async () => { const entryWithFiles = generateMockEntry(1, { id: 1, '5': 'https://example.com/uploads/file1.pdf', '6': JSON.stringify([ 'https://example.com/uploads/file2.jpg', 'https://example.com/uploads/file3.png' ]) }); mockHttpClient.setMockResponse('GET', '/entries/1', new MockResponse(entryWithFiles)); const result = await client.getEntry({ id: 1 }); TestAssert.includes(result.entry['5'], 'file1.pdf'); }); suite.test('Get Entry: Should handle entry with payment fields', async () => { const paymentEntry = generateMockEntry(1, { id: 1, payment_status: 'Paid', payment_amount: '99.99', payment_date: '2024-01-15', transaction_id: 'TXN123456' }); mockHttpClient.setMockResponse('GET', '/entries/1', new MockResponse(paymentEntry)); const result = await client.getEntry({ id: 1 }); TestAssert.equal(result.entry.payment_status, 'Paid'); TestAssert.equal(result.entry.payment_amount, '99.99'); }); suite.test('Get Entry: Should handle non-existent entry (404)', async () => { mockHttpClient.setMockResponse('GET', '/entries/999', new MockResponse( { message: 'Entry not found' }, 404 )); await TestAssert.throwsAsync( () => client.getEntry({ id: 999 }), 'not found', 'Should handle 404 error' ); }); // ================================= // CREATE ENTRY TESTS // ================================= suite.test('Create Entry: Should create new entry with field values', async () => { const newEntry = generateMockEntry(1, { id: 500 }); mockHttpClient.setMockResponse('POST', '/entries', new MockResponse(newEntry)); const result = await client.createEntry({ form_id: 1, '1': 'Jane Doe', '2': 'jane@example.com', '3': 'Test message', created_by: 1 }); TestAssert.isTrue(result.created); TestAssert.equal(result.entry.id, 500); TestAssert.equal(result.message, 'Entry created successfully'); }); suite.test('Create Entry: Should require form_id', async () => { await TestAssert.throwsAsync( () => client.createEntry({ '1': 'Test' }), 'form_id', 'Should require form_id' ); }); suite.test('Create Entry: Should validate field values', async () => { mockHttpClient.setMockResponse('POST', '/entries', new MockResponse( { message: 'Field validation failed', field_errors: ['Email is invalid'] }, 400 )); await TestAssert.throwsAsync( () => client.createEntry({ form_id: 1, '2': 'invalid-email' }), 'validation', 'Should handle validation errors' ); }); suite.test('Create Entry: Should handle complex field types', async () => { const complexEntry = { form_id: 1, '1.3': 'First Name', '1.6': 'Last Name', '2': ['choice1', 'choice2'], '3': JSON.stringify({ street: '123 Main St', city: 'Anytown' }), '4': '2024-01-15', created_by: 1 }; mockHttpClient.setMockResponse('POST', '/entries', new MockResponse({ ...complexEntry, id: 600 })); const result = await client.createEntry(complexEntry); TestAssert.isTrue(result.created); TestAssert.equal(result.entry['1.3'], 'First Name'); }); // ================================= // UPDATE ENTRY TESTS // ================================= suite.test('Update Entry: Should update existing entry', async () => { // First mock the GET request to fetch existing entry const existingEntry = generateMockEntry(1, { id: 100, '1': 'Original Name', '2': 'original@email.com', '3': 'Original Address', status: 'active' }); mockHttpClient.setMockResponse('GET', '/entries/100', new MockResponse(existingEntry)); // Then mock the PUT request with the merged data const updatedEntry = generateMockEntry(1, { id: 100, '1': 'Updated Name', '2': 'original@email.com', // Preserved '3': 'Original Address', // Preserved status: 'spam' }); mockHttpClient.setMockResponse('PUT', '/entries/100', new MockResponse(updatedEntry)); const result = await client.updateEntry({ id: 100, '1': 'Updated Name', status: 'spam' }); TestAssert.isTrue(result.updated); TestAssert.equal(result.entry['1'], 'Updated Name'); TestAssert.equal(result.message, 'Entry updated successfully'); }); suite.test('Update Entry: Should preserve all field data when updating single field', async () => { // Mock existing entry with multiple fields const existingEntry = { id: 23, form_id: 9, '1': 'John Doe', '2': 'john@example.com', '3': '25', date_created: '2025-09-09 22:00:29', date_updated: '2025-09-09 22:00:29', is_starred: '0', is_read: '0', status: 'active' }; mockHttpClient.setMockResponse('GET', '/entries/23', new MockResponse(existingEntry)); // Expected merged data (all fields preserved, only is_starred updated) const expectedMergedData = { ...existingEntry, is_starred: '1' }; mockHttpClient.setMockResponse('PUT', '/entries/23', new MockResponse(expectedMergedData)); // Update only the is_starred field const result = await client.updateEntry({ id: 23, is_starred: '1' }); // Verify the PUT request was made with ALL data const putRequest = mockHttpClient.getRequests().find(r => r.method === 'PUT'); TestAssert.exists(putRequest, 'PUT request should be made'); TestAssert.equal(putRequest.config.data['1'], 'John Doe', 'Field 1 should be preserved'); TestAssert.equal(putRequest.config.data['2'], 'john@example.com', 'Field 2 should be preserved'); TestAssert.equal(putRequest.config.data['3'], '25', 'Field 3 should be preserved'); TestAssert.equal(putRequest.config.data.is_starred, '1', 'is_starred should be updated'); TestAssert.isTrue(result.updated); TestAssert.equal(result.entry['1'], 'John Doe', 'Original field data preserved in response'); TestAssert.equal(result.entry.is_starred, '1', 'Updated field changed in response'); }); suite.test('Update Entry: Should preserve metadata when updating fields', async () => { const entry = generateMockEntry(1, { id: 1, date_created: '2024-01-01T00:00:00Z', created_by: 1 }); mockHttpClient.setMockResponse('PUT', '/entries/1', new MockResponse(entry)); const result = await client.updateEntry({ id: 1, '1': 'New Value' }); TestAssert.equal(result.entry.date_created, '2024-01-01T00:00:00Z'); TestAssert.equal(result.entry.created_by, 1); }); suite.test('Update Entry: Should validate entry status', async () => { await TestAssert.throwsAsync( () => client.updateEntry({ id: 1, status: 'invalid_status' }), 'Invalid status', 'Should validate status values' ); }); // ================================= // DELETE ENTRY TESTS // ================================= suite.test('Delete Entry: Should trash entry by default', async () => { mockHttpClient.setMockResponse('DELETE', '/entries/1', new MockResponse({})); const result = await client.deleteEntry({ id: 1 }); TestAssert.isTrue(result.deleted); TestAssert.isFalse(result.permanently); TestAssert.equal(result.message, 'Entry moved to trash'); }); suite.test('Delete Entry: Should permanently delete with force=true', async () => { mockHttpClient.setMockResponse('DELETE', '/entries/1', new MockResponse({})); const result = await client.deleteEntry({ id: 1, force: true }); TestAssert.isTrue(result.deleted); TestAssert.isTrue(result.permanently); TestAssert.equal(result.message, 'Entry permanently deleted'); }); suite.test('Delete Entry: Should require ALLOW_DELETE=true', async () => { client.allowDelete = false; await TestAssert.throwsAsync( () => client.deleteEntry({ id: 1 }), 'Delete operations are disabled', 'Should check delete permission' ); }); // ================================= // EDGE CASES AND FAILURE MODES // ================================= suite.test('Edge Case: Should handle large datasets (1000+ entries)', async () => { const largeDataset = Array.from({ length: 1000 }, (_, i) => generateMockEntry(1, { id: i + 1 }) ); mockHttpClient.setMockResponse('GET', '/entries', new MockResponse({ entries: largeDataset, total_count: 1000 })); const result = await client.listEntries({ paging: { page_size: 200 } }); TestAssert.equal(result.total_count, 1000); }); suite.test('Edge Case: Should handle date boundary searches', async () => { const search = { field_filters: [ { key: 'date_created', value: '2024-01-01T00:00:00Z', operator: '>=' }, { key: 'date_created', value: '2024-12-31T23:59:59Z', operator: '<=' } ] }; mockHttpClient.setMockResponse('GET', '/entries', new MockResponse({ entries: [] })); const result = await client.listEntries({ search }); TestAssert.isNotNull(result.search_criteria); }); suite.test('Edge Case: Should handle multi-page form entries', async () => { const multiPageEntry = generateMockEntry(1, { id: 1, page_number: 3, resume_token: 'abc123def456' }); mockHttpClient.setMockResponse('GET', '/entries/1', new MockResponse(multiPageEntry)); const result = await client.getEntry({ id: 1 }); TestAssert.equal(result.entry.page_number, 3); TestAssert.equal(result.entry.resume_token, 'abc123def456'); }); suite.test('Failure Mode: Should handle permission errors', async () => { mockHttpClient.setMockResponse('GET', '/entries/1', new MockResponse( { message: 'You do not have permission to view this entry' }, 403 )); await TestAssert.throwsAsync( () => client.getEntry({ id: 1 }), 'permission', 'Should handle permission errors' ); }); suite.test('Failure Mode: Should handle database errors', async () => { mockHttpClient.setMockResponse('POST', '/entries', new MockResponse( { message: 'Database connection failed' }, 500 )); await TestAssert.throwsAsync( () => client.createEntry({ form_id: 1 }), 'Server error', 'Should handle database errors' ); }); // Run tests suite.run().then(results => { process.exit(results.failed > 0 ? 1 : 0); }); export default suite;

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/GravityKit/gravity-mcp'

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