Skip to main content
Glama
cloudflare.test.ts15.7 kB
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; vi.mock('@aws-sdk/client-s3', () => { return { S3Client: vi.fn().mockImplementation(() => ({ send: vi.fn(), })), PutObjectCommand: vi.fn().mockImplementation((params) => ({ params })), HeadObjectCommand: vi.fn().mockImplementation((params) => ({ params })), }; }); import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; import { CloudflareEnvConfigSchema, CloudflareUploadService, ValidatedCloudflareEnvConfig } from '../../src/services/cloudflare'; import { UploadImageArgs } from '../../src/services/types'; // Get the mocked constructors const MockedS3Client = vi.mocked(S3Client); const MockedPutObjectCommand = vi.mocked(PutObjectCommand); describe('CloudflareUploadService', () => { let service: CloudflareUploadService; let mockValidatedConfig: ValidatedCloudflareEnvConfig; let mockBuffer: Buffer; let mockArgs: UploadImageArgs; let mockSend: ReturnType<typeof vi.fn>; beforeEach(() => { vi.clearAllMocks(); mockSend = vi.fn(); MockedS3Client.mockImplementation(() => ({ send: mockSend, }) as any); mockValidatedConfig = CloudflareEnvConfigSchema.parse({ CLOUDFLARE_R2_BUCKET: 'test-bucket', CLOUDFLARE_R2_ACCESS_KEY_ID: 'test-access-key', CLOUDFLARE_R2_SECRET_ACCESS_KEY: 'test-secret-key', CLOUDFLARE_R2_ENDPOINT: 'https://test-account.r2.cloudflarestorage.com', CLOUDFLARE_R2_REGION: 'auto', }); mockBuffer = Buffer.from('test image data'); mockArgs = { public: true, overwrite: false, metadata: { userId: '123' }, tags: ['test', 'image'], }; }); afterEach(() => { vi.restoreAllMocks(); }); describe('constructor', () => { it('should create S3Client with correct Cloudflare R2 configuration', () => { service = new CloudflareUploadService(mockValidatedConfig); expect(MockedS3Client).toHaveBeenCalledWith({ region: 'auto', credentials: { accessKeyId: 'test-access-key', secretAccessKey: 'test-secret-key', }, endpoint: 'https://test-account.r2.cloudflarestorage.com', forcePathStyle: true, // Required for R2 }); }); it('should use default region if not provided', () => { const configWithoutRegion = { ...mockValidatedConfig }; delete configWithoutRegion.CLOUDFLARE_R2_REGION; service = new CloudflareUploadService(configWithoutRegion); expect(MockedS3Client).toHaveBeenCalledWith({ region: 'auto', // Defaulted by the service constructor credentials: { accessKeyId: mockValidatedConfig.CLOUDFLARE_R2_ACCESS_KEY_ID, secretAccessKey: mockValidatedConfig.CLOUDFLARE_R2_SECRET_ACCESS_KEY, }, endpoint: mockValidatedConfig.CLOUDFLARE_R2_ENDPOINT, forcePathStyle: true, }); }); }); describe('upload', () => { beforeEach(() => { service = new CloudflareUploadService(mockValidatedConfig); }); it('should successfully upload file without folder', async () => { // Mock HeadObject to throw NotFound (file doesn't exist) const notFoundError = new Error('Not Found'); notFoundError.name = 'NotFound'; mockSend.mockRejectedValueOnce(notFoundError); // Mock successful upload const mockResult = { ETag: '"test-etag"', VersionId: 'test-version-id', }; mockSend.mockResolvedValueOnce(mockResult); const result = await service.upload(mockBuffer, 'test.jpg', mockArgs); expect(result).toEqual({ url: `https://test-account.r2.cloudflarestorage.com/${mockValidatedConfig.CLOUDFLARE_R2_BUCKET}/test.jpg`, filename: 'test.jpg', size: mockBuffer.length, format: 'jpg', service: 'cloudflare', metadata: { bucket: mockValidatedConfig.CLOUDFLARE_R2_BUCKET, key: 'test.jpg', endpoint: mockValidatedConfig.CLOUDFLARE_R2_ENDPOINT, etag: '"test-etag"', versionId: 'test-version-id', }, }); }); it('should successfully upload file with folder', async () => { // Mock HeadObject to throw NotFound (file doesn't exist) const notFoundError = new Error('Not Found'); notFoundError.name = 'NotFound'; mockSend.mockRejectedValueOnce(notFoundError); // Mock successful upload const mockResult = { ETag: '"test-etag"', VersionId: 'test-version-id', }; mockSend.mockResolvedValueOnce(mockResult); const argsWithFolder = { ...mockArgs, folder: 'uploads/2024' }; const result = await service.upload(mockBuffer, 'test.jpg', argsWithFolder); expect(result.url).toBe(`${mockValidatedConfig.CLOUDFLARE_R2_ENDPOINT}/${mockValidatedConfig.CLOUDFLARE_R2_BUCKET}/uploads/2024/test.jpg`); expect(result.metadata?.key).toBe('uploads/2024/test.jpg'); }); it('should generate custom domain URL when baseUrl is provided', async () => { const tempService = new CloudflareUploadService(mockValidatedConfig); (tempService as any).config.baseUrl = 'https://cdn.example.com'; // Mock HeadObject to throw NotFound (file doesn't exist) const notFoundError = new Error('Not Found'); notFoundError.name = 'NotFound'; mockSend.mockRejectedValueOnce(notFoundError); const mockResult = { ETag: '"test-etag"' }; mockSend.mockResolvedValueOnce(mockResult); const result = await tempService.upload(mockBuffer, 'test.jpg', mockArgs); expect(result.url).toBe('https://cdn.example.com/test.jpg'); }); it('should handle file extension correctly', async () => { // Mock HeadObject to throw NotFound (file doesn't exist) const notFoundError = new Error('Not Found'); notFoundError.name = 'NotFound'; mockSend.mockRejectedValueOnce(notFoundError); const mockResult = { ETag: '"test-etag"' }; mockSend.mockResolvedValueOnce(mockResult); const result = await service.upload(mockBuffer, 'test.PNG', mockArgs); expect(result.format).toBe('png'); }); it('should default to jpg extension if none provided', async () => { // Mock HeadObject to throw NotFound (file doesn't exist) const notFoundError = new Error('Not Found'); notFoundError.name = 'NotFound'; mockSend.mockRejectedValueOnce(notFoundError); const mockResult = { ETag: '"test-etag"' }; mockSend.mockResolvedValueOnce(mockResult); const result = await service.upload(mockBuffer, 'test', mockArgs); expect(result.format).toBe('jpg'); }); it('should set correct content type for different file types', async () => { // Mock HeadObject to throw NotFound (file doesn't exist) const notFoundError = new Error('Not Found'); notFoundError.name = 'NotFound'; mockSend.mockRejectedValueOnce(notFoundError); const mockResult = { ETag: '"test-etag"' }; mockSend.mockResolvedValueOnce(mockResult); await service.upload(mockBuffer, 'test.png', mockArgs); expect(MockedPutObjectCommand).toHaveBeenCalledWith( expect.objectContaining({ ContentType: 'image/png', }) ); }); it('should not include ACL in upload command (R2 specific)', async () => { // Mock HeadObject to throw NotFound (file doesn't exist) const notFoundError = new Error('Not Found'); notFoundError.name = 'NotFound'; mockSend.mockRejectedValueOnce(notFoundError); const mockResult = { ETag: '"test-etag"' }; mockSend.mockResolvedValueOnce(mockResult); await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, public: true }); expect(MockedPutObjectCommand).toHaveBeenCalledWith( expect.not.objectContaining({ ACL: expect.anything(), }) ); }); it('should include metadata in upload command', async () => { // Mock HeadObject to throw NotFound (file doesn't exist) const notFoundError = new Error('Not Found'); notFoundError.name = 'NotFound'; mockSend.mockRejectedValueOnce(notFoundError); const mockResult = { ETag: '"test-etag"' }; mockSend.mockResolvedValueOnce(mockResult); const metadata = { userId: '123', uploadType: 'profile' }; await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, metadata }); expect(MockedPutObjectCommand).toHaveBeenCalledWith( expect.objectContaining({ Metadata: metadata, }) ); }); it('should not include tags in upload command (R2 limitation)', async () => { // Mock HeadObject to throw NotFound (file doesn't exist) const notFoundError = new Error('Not Found'); notFoundError.name = 'NotFound'; mockSend.mockRejectedValueOnce(notFoundError); const mockResult = { ETag: '"test-etag"' }; mockSend.mockResolvedValueOnce(mockResult); const tags = ['user-upload', 'profile-pic']; await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, tags }); // R2 doesn't support tagging in the same way as S3 expect(MockedPutObjectCommand).toHaveBeenCalledWith( expect.not.objectContaining({ Tagging: expect.anything(), }) ); }); describe('overwrite protection', () => { it('should check for existing file when overwrite is false', async () => { // Mock HeadObject to throw NotFound (file doesn't exist) const notFoundError = new Error('Not Found'); notFoundError.name = 'NotFound'; mockSend.mockRejectedValueOnce(notFoundError); // Mock successful upload const mockResult = { ETag: '"test-etag"' }; mockSend.mockResolvedValueOnce(mockResult); await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, overwrite: false }); expect(mockSend).toHaveBeenCalledTimes(2); // HeadObject + PutObject }); it('should throw error if file exists and overwrite is false', async () => { // Mock HeadObject to succeed (file exists) mockSend.mockResolvedValueOnce({ ContentLength: 1000 }); await expect( service.upload(mockBuffer, 'test.jpg', { ...mockArgs, overwrite: false }) ).rejects.toThrow(McpError); await expect( service.upload(mockBuffer, 'test.jpg', { ...mockArgs, overwrite: false }) ).rejects.toThrow('File test.jpg already exists. Set overwrite=true to replace it.'); }); it('should skip existence check when overwrite is true', async () => { const mockResult = { ETag: '"test-etag"' }; mockSend.mockResolvedValue(mockResult); await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, overwrite: true }); expect(mockSend).toHaveBeenCalledTimes(1); // Only PutObject }); it('should handle NoSuchKey error as file not found', async () => { // Mock HeadObject to throw NoSuchKey (file doesn't exist) const noSuchKeyError = new Error('No Such Key'); noSuchKeyError.name = 'NoSuchKey'; mockSend.mockRejectedValueOnce(noSuchKeyError); // Mock successful upload const mockResult = { ETag: '"test-etag"' }; mockSend.mockResolvedValueOnce(mockResult); const result = await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, overwrite: false }); expect(result).toBeDefined(); expect(mockSend).toHaveBeenCalledTimes(2); }); }); describe('error handling', () => { it('should wrap R2 errors in McpError', async () => { const r2Error = new Error('R2 Service Error'); mockSend.mockRejectedValue(r2Error); await expect( service.upload(mockBuffer, 'test.jpg', mockArgs) ).rejects.toThrow(McpError); await expect( service.upload(mockBuffer, 'test.jpg', mockArgs) ).rejects.toThrow('Cloudflare R2 upload failed: R2 Service Error'); }); it('should preserve McpError instances', async () => { const mcpError = new McpError(ErrorCode.InvalidParams, 'Custom MCP error'); mockSend.mockRejectedValue(mcpError); await expect( service.upload(mockBuffer, 'test.jpg', mockArgs) ).rejects.toThrow(mcpError); }); }); }); describe('getContentType', () => { beforeEach(() => { service = new CloudflareUploadService(mockValidatedConfig); }); it('should return correct MIME types for supported formats', () => { // Access private method for testing const getContentType = (service as any).getContentType.bind(service); expect(getContentType('jpg')).toBe('image/jpeg'); expect(getContentType('jpeg')).toBe('image/jpeg'); expect(getContentType('png')).toBe('image/png'); expect(getContentType('gif')).toBe('image/gif'); expect(getContentType('webp')).toBe('image/webp'); expect(getContentType('avif')).toBe('image/avif'); expect(getContentType('tiff')).toBe('image/tiff'); expect(getContentType('heic')).toBe('image/heic'); expect(getContentType('heif')).toBe('image/heif'); }); it('should return default MIME type for unknown extensions', () => { const getContentType = (service as any).getContentType.bind(service); expect(getContentType('unknown')).toBe('application/octet-stream'); }); }); describe('generateUrl', () => { it('should generate R2 endpoint URL by default', () => { service = new CloudflareUploadService(mockValidatedConfig); const generateUrl = (service as any).generateUrl.bind(service); const url = generateUrl('test/image.jpg'); expect(url).toBe(`${mockValidatedConfig.CLOUDFLARE_R2_ENDPOINT}/${mockValidatedConfig.CLOUDFLARE_R2_BUCKET}/test/image.jpg`); }); it('should generate custom domain URL when baseUrl is provided', () => { // Similar to the upload test, this requires setting internal config.baseUrl const serviceWithBaseUrl = new CloudflareUploadService(mockValidatedConfig); (serviceWithBaseUrl as any).config.baseUrl = 'https://cdn.example.com'; const generateUrl = (serviceWithBaseUrl as any).generateUrl.bind(serviceWithBaseUrl); const url = generateUrl('test/image.jpg'); expect(url).toBe('https://cdn.example.com/test/image.jpg'); }); it('should handle keys without folders', () => { service = new CloudflareUploadService(mockValidatedConfig); const generateUrl = (service as any).generateUrl.bind(service); const url = generateUrl('image.jpg'); expect(url).toBe(`${mockValidatedConfig.CLOUDFLARE_R2_ENDPOINT}/${mockValidatedConfig.CLOUDFLARE_R2_BUCKET}/image.jpg`); }); }); describe('Cloudflare R2 specific features', () => { beforeEach(() => { service = new CloudflareUploadService(mockValidatedConfig); }); it('should always use forcePathStyle for R2 compatibility', () => { expect(MockedS3Client).toHaveBeenCalledWith( expect.objectContaining({ forcePathStyle: true, }) ); }); it('should use auto region by default for R2', () => { const envConfigWithoutRegion = { ...mockValidatedConfig }; delete envConfigWithoutRegion.CLOUDFLARE_R2_REGION; service = new CloudflareUploadService(envConfigWithoutRegion); expect(MockedS3Client).toHaveBeenCalledWith( expect.objectContaining({ region: 'auto', }) ); }); }); });

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/BoomLinkAi/image-worker-mcp'

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