Skip to main content
Glama
aws-powertools

Powertools MCP Search Server

fetchWithCache.test.ts6.95 kB
import { HttpResponse, http } from 'msw'; import { setupServer } from 'msw/node'; import { afterAll, afterEach, beforeAll, describe, expect, it, vi, } from 'vitest'; import { CACHE_BASE_PATH, POWERTOOLS_BASE_URL } from '../../src/constants.ts'; import { fetchWithCache } from '../../src/tools/shared/fetchWithCache.ts'; const mocks = vi.hoisted(() => ({ getFromCache: vi.fn(), writeToCache: vi.fn(), })); vi.mock('cacache', async (importOriginal) => ({ ...(await importOriginal<typeof import('cacache')>()), get: mocks.getFromCache, put: mocks.writeToCache, })); describe('fetchWithCache', () => { const baseUrl = `${POWERTOOLS_BASE_URL}/typescript/latest/features/`; const server = setupServer( ...[ http.get( `${POWERTOOLS_BASE_URL}/typescript/latest/features/metrics/index.md`, () => HttpResponse.text( 'Metrics is a feature of PowerTools for TypeScript.', { headers: { 'Content-Type': 'text/markdown', ETag: '"12345"', }, } ) ), http.head( `${POWERTOOLS_BASE_URL}/typescript/latest/features/metrics/index.md`, () => HttpResponse.text(null, { headers: { 'Content-Type': 'text/markdown', ETag: '"12345"', }, }) ), http.get( `${POWERTOOLS_BASE_URL}/typescript/latest/features/no-etag-for-some-reason.md`, () => HttpResponse.text( 'Metrics is a feature of PowerTools for TypeScript.' ) ), http.get( `${POWERTOOLS_BASE_URL}/typescript/latest/features/non-existent`, () => HttpResponse.json({ error: 'Not found' }, { status: 404 }) ), ] ); beforeAll(() => server.listen()); afterEach(() => { server.resetHandlers(); vi.clearAllMocks(); }); afterAll(() => server.close()); it('returns cached resource if ETag matches', async () => { // Prepare mocks.getFromCache.mockResolvedValueOnce({ data: Buffer.from('12345'), }); const expectedContent = 'Metrics is a feature of PowerTools for TypeScript.'; mocks.getFromCache.mockResolvedValueOnce({ data: Buffer.from(expectedContent), }); const url = new URL(`${baseUrl}metrics/index.md`); const cacheKey = url.pathname; // Act const result = await fetchWithCache({ url, contentType: 'text/markdown' }); // Assess expect(result).toEqual(expectedContent); expect(mocks.getFromCache).toHaveBeenNthCalledWith( 1, expect.stringContaining(CACHE_BASE_PATH), `${cacheKey}-etag` ); expect(mocks.getFromCache).toHaveBeenNthCalledWith( 2, expect.stringContaining(CACHE_BASE_PATH), `${cacheKey}` ); expect(mocks.writeToCache).not.toHaveBeenCalled(); expect(console.debug).toHaveLogged( expect.objectContaining({ message: 'cached eTag matches, returning cached resource', }) ); }); it('skips cache if both cached and remote ETags are null', async () => { // Prepare mocks.getFromCache.mockRejectedValueOnce(new Error('Cache miss')); const url = new URL(`${baseUrl}no-etag-for-some-reason.md`); const cacheKey = url.pathname; // Act const result = await fetchWithCache({ url, contentType: 'text/markdown' }); // Assess expect(result).toEqual( 'Metrics is a feature of PowerTools for TypeScript.' ); expect(mocks.getFromCache).toHaveBeenCalledExactlyOnceWith( expect.stringContaining(CACHE_BASE_PATH), `${cacheKey}-etag` ); expect(console.debug).toHaveLogged( expect.objectContaining({ message: 'No cached ETag and remote ETag found, fetching remote resource', }) ); }); it('fetches a resource and updates the cache if ETag does not match', async () => { // Prepare mocks.getFromCache.mockResolvedValueOnce({ data: Buffer.from('54321'), }); const url = new URL(`${baseUrl}metrics/index.md`); const cacheKey = url.pathname; const expectedContent = 'Metrics is a feature of PowerTools for TypeScript.'; // Act const result = await fetchWithCache({ url, contentType: 'text/markdown' }); // Assess expect(result).toEqual(expectedContent); expect(mocks.getFromCache).toHaveBeenCalledExactlyOnceWith( expect.stringContaining(CACHE_BASE_PATH), `${cacheKey}-etag` ); expect(mocks.writeToCache).toHaveBeenNthCalledWith( 1, expect.stringContaining(CACHE_BASE_PATH), `${cacheKey}-etag`, '12345' ); expect(mocks.writeToCache).toHaveBeenNthCalledWith( 2, expect.stringContaining(CACHE_BASE_PATH), cacheKey, expectedContent ); expect(console.debug).toHaveLogged( expect.objectContaining({ message: 'ETag mismatch: local 54321 vs remote 12345; fetching remote resource', }) ); }); it('fetches a resource anyway if the cache is incomplete', async () => { // Prepare mocks.getFromCache.mockResolvedValueOnce({ data: Buffer.from('12345'), }); mocks.getFromCache.mockRejectedValueOnce(new Error('Cache miss')); const url = new URL(`${baseUrl}metrics/index.md`); const cacheKey = url.pathname; const expectedContent = 'Metrics is a feature of PowerTools for TypeScript.'; // Act const result = await fetchWithCache({ url, contentType: 'text/markdown' }); // Assess expect(result).toEqual(expectedContent); expect(mocks.getFromCache).toHaveBeenNthCalledWith( 1, expect.stringContaining(CACHE_BASE_PATH), `${cacheKey}-etag` ); expect(mocks.getFromCache).toHaveBeenNthCalledWith( 2, expect.stringContaining(CACHE_BASE_PATH), cacheKey ); expect(mocks.writeToCache).toHaveBeenNthCalledWith( 1, expect.stringContaining(CACHE_BASE_PATH), `${cacheKey}-etag`, '12345' ); expect(mocks.writeToCache).toHaveBeenNthCalledWith( 2, expect.stringContaining(CACHE_BASE_PATH), cacheKey, expectedContent ); expect(console.debug).toHaveLogged( expect.objectContaining({ message: 'Cached resource not found even though ETag matches; cache may be corrupted', }) ); }); it('throws when unable to fetch remote resource', async () => { // Prepare mocks.getFromCache.mockResolvedValueOnce(null); const url = new URL(`${baseUrl}non-existent`); // Act & Assess await expect( fetchWithCache({ url, contentType: 'text/markdown' }) ).rejects.toThrow( expect.objectContaining({ message: 'Failed to fetch remote resource', cause: expect.objectContaining({ message: `Request to fetch ${url.toString()} failed: 404 Not Found`, }), }) ); }); });

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/aws-powertools/powertools-mcp'

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