UNPKG

lamplighter-mcp

Version:

An intelligent context engine for AI-assisted software development

171 lines (143 loc) 6.86 kB
import axios from 'axios'; import { ConfluenceReader } from '../../src/modules/confluenceReader'; import dotenv from 'dotenv'; // Ensure dotenv is configured for tests if needed, or mock process.env directly dotenv.config(); // Mock axios jest.mock('axios'); const mockedAxios = jest.mocked(axios); // Save original environment variables const originalEnv = process.env; describe('ConfluenceReader', () => { beforeEach(() => { // Reset mocks and environment variables before each test jest.clearAllMocks(); process.env = { ...originalEnv, CONFLUENCE_URL: 'https://example.atlassian.net/wiki', CONFLUENCE_USERNAME: 'testuser@example.com', CONFLUENCE_API_TOKEN: 'testtoken' }; }); afterAll(() => { // Restore original environment variables process.env = originalEnv; }); it('should throw error if CONFLUENCE_URL is not set', () => { delete process.env.CONFLUENCE_URL; expect(() => new ConfluenceReader()).toThrow('Confluence URL is required'); }); it('should throw error if CONFLUENCE_USERNAME is not set', () => { delete process.env.CONFLUENCE_USERNAME; expect(() => new ConfluenceReader()).toThrow('Confluence username is required'); }); it('should throw error if CONFLUENCE_API_TOKEN is not set', () => { delete process.env.CONFLUENCE_API_TOKEN; expect(() => new ConfluenceReader()).toThrow('Confluence API token is required'); }); describe('extractPageId', () => { let reader: ConfluenceReader; beforeEach(() => { reader = new ConfluenceReader(); // Access private method for testing (TypeScript workaround) reader['extractPageId'] = reader['extractPageId'].bind(reader); }); it.each([ ['https://example.atlassian.net/wiki/spaces/SPACE/pages/123456/Page+Title', '123456'], ['https://example.atlassian.net/wiki/spaces/SPACE/pages/98765', '98765'], ['https://example.atlassian.net/wiki/pages/view/54321', '54321'], ['https://example.atlassian.net/wiki/pages?pageId=67890', '67890'], ['112233', '112233'] // Direct ID ])('should extract page ID %s correctly', (urlOrId, expectedId) => { expect(reader['extractPageId'](urlOrId)).toBe(expectedId); }); it('should throw error for invalid URL/ID', () => { expect(() => reader['extractPageId']('invalid-url')).toThrow('Invalid Confluence URL format: invalid-url'); expect(() => reader['extractPageId']('https://example.com/no/id/here')).toThrow('Could not extract page ID from URL: https://example.com/no/id/here'); }); }); describe('fetchPageContent', () => { let reader: ConfluenceReader; const pageId = '123456'; const pageUrl = `https://example.atlassian.net/wiki/spaces/SPACE/pages/${pageId}/Page+Title`; const apiUrl = `https://example.atlassian.net/wiki/rest/api/content/${pageId}?expand=body.storage`; const expectedAuth = `Basic ${Buffer.from('testuser@example.com:testtoken').toString('base64')}`; beforeEach(() => { reader = new ConfluenceReader(); }); it('should fetch and extract content successfully', async () => { const mockHtml = '<p>Hello &nbsp; <b>World</b>!&quot;</p><br/><p>Test.</p>'; const expectedText = 'Hello World!"\n\nTest.'; mockedAxios.get.mockResolvedValue({ data: { title: 'Test Page', body: { storage: { value: mockHtml } } } }); const content = await reader.fetchPageContent(pageUrl); expect(mockedAxios.get).toHaveBeenCalledTimes(1); expect(mockedAxios.get).toHaveBeenCalledWith(apiUrl, { headers: { 'Authorization': expectedAuth, 'Content-Type': 'application/json' } }); expect(content).toBe(expectedText); }); // it('should handle Confluence API error (e.g., 404)', async () => { // const errorResponse = { // response: { // status: 404, // data: { message: 'Page not found' } // } // }; // const axiosError = Object.assign(new Error('Request failed with status code 404'), errorResponse, { isAxiosError: true, config: {}, request: {} }); // mockedAxios.get.mockRejectedValue(axiosError); // await expect(reader.fetchPageContent(pageUrl)) // .rejects.toThrow('Confluence API error: 404 - Page not found'); // expect(mockedAxios.get).toHaveBeenCalledTimes(1); // expect(mockedAxios.get).toHaveBeenCalledWith(apiUrl, expect.any(Object)); // }); // it('should handle network error', async () => { // const errorRequest = { request: {} }; // Simulate request made but no response // const networkError = Object.assign(new Error('Network Error'), errorRequest, { isAxiosError: true, config: {} }); // mockedAxios.get.mockRejectedValue(networkError); // await expect(reader.fetchPageContent(pageUrl)) // .rejects.toThrow('No response received from Confluence. Please check your network connection and Confluence URL.'); // expect(mockedAxios.get).toHaveBeenCalledTimes(1); // }); it('should handle generic error', async () => { const genericError = new Error('Something else failed'); mockedAxios.get.mockRejectedValue(genericError); await expect(reader.fetchPageContent(pageUrl)).rejects.toThrow(`Failed to fetch Confluence page: ${genericError.message}`); expect(mockedAxios.get).toHaveBeenCalledTimes(1); }); }); describe('extractTextFromHtml', () => { let reader: ConfluenceReader; beforeEach(() => { reader = new ConfluenceReader(); // Access private method for testing (TypeScript workaround) reader['extractTextFromHtml'] = reader['extractTextFromHtml'].bind(reader); }); it('should extract text from basic HTML', () => { const html = '<p>Line 1.</p> <p>Line <b>2</b> with &amp; entities&nbsp;and<br/>break.</p><div>Div line</div>'; const expected = 'Line 1.\n\nLine 2 with & entities and\nbreak.\n\nDiv line'; expect(reader['extractTextFromHtml'](html)).toBe(expected); }); it('should handle empty or only whitespace HTML', () => { expect(reader['extractTextFromHtml'](null as any)).toBe(''); expect(reader['extractTextFromHtml']('')).toBe(''); expect(reader['extractTextFromHtml'](' <p> </p> ')).toBe(''); }); it('should strip complex tags and handle spacing', () => { const html = '<script>alert("bad")</script><div><a href="#">Link</a> Text<span> More</span></div><p>Paragraph</p>'; const expected = 'Link Text More\n\nParagraph'; expect(reader['extractTextFromHtml'](html)).toBe(expected); }); }); });