Skip to main content
Glama
gcloud.test.ts14.5 kB
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; // Mock Google Cloud Storage vi.mock('@google-cloud/storage', () => { const mockFile = { save: vi.fn(), exists: vi.fn(), }; const mockBucket = { file: vi.fn().mockReturnValue(mockFile), }; const mockStorage = { bucket: vi.fn().mockReturnValue(mockBucket), }; return { Storage: vi.fn().mockImplementation(() => mockStorage), }; }); import { Storage } from '@google-cloud/storage'; import { GCloudEnvConfigSchema, GCloudUploadService, ValidatedGCloudEnvConfig } from '../../src/services/gcloud'; import { UploadImageArgs } from '../../src/services/types'; // Get the mocked constructors const MockedStorage = vi.mocked(Storage); describe('GCloudUploadService', () => { let service: GCloudUploadService; let mockValidatedConfig: ValidatedGCloudEnvConfig; let mockBuffer: Buffer; let mockArgs: UploadImageArgs; let mockStorage: any; let mockBucket: any; let mockFile: any; beforeEach(() => { vi.clearAllMocks(); mockFile = { save: vi.fn(), exists: vi.fn(), }; mockBucket = { file: vi.fn().mockReturnValue(mockFile), }; mockStorage = { bucket: vi.fn().mockReturnValue(mockBucket), }; MockedStorage.mockImplementation(() => mockStorage); mockValidatedConfig = GCloudEnvConfigSchema.parse({ GCLOUD_BUCKET: 'test-bucket', GCLOUD_PROJECT_ID: 'test-project-id', }); 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 Storage client with correct configuration', () => { service = new GCloudUploadService(mockValidatedConfig); expect(MockedStorage).toHaveBeenCalledWith({ projectId: 'test-project-id', }); expect(mockStorage.bucket).toHaveBeenCalledWith('test-bucket'); }); it('should include keyFilename if GCLOUD_CREDENTIALS_PATH is provided', () => { const configWithCredentials = GCloudEnvConfigSchema.parse({ GCLOUD_BUCKET: 'test-bucket', GCLOUD_PROJECT_ID: 'test-project-id', GCLOUD_CREDENTIALS_PATH: '/path/to/credentials.json', }); service = new GCloudUploadService(configWithCredentials); expect(MockedStorage).toHaveBeenCalledWith({ projectId: 'test-project-id', keyFilename: '/path/to/credentials.json', }); }); it('should not include keyFilename if GCLOUD_CREDENTIALS_PATH is not provided', () => { service = new GCloudUploadService(mockValidatedConfig); expect(MockedStorage).toHaveBeenCalledWith({ projectId: 'test-project-id', }); // Ensure keyFilename is not in the call const calledWith = MockedStorage.mock.calls[0][0]; expect(calledWith).not.toHaveProperty('keyFilename'); }); }); describe('upload', () => { beforeEach(() => { service = new GCloudUploadService(mockValidatedConfig); }); it('should successfully upload file without folder', async () => { // Mock file doesn't exist mockFile.exists.mockResolvedValue([false]); // Mock successful upload mockFile.save.mockResolvedValue(undefined); const result = await service.upload(mockBuffer, 'test.jpg', mockArgs); expect(mockBucket.file).toHaveBeenCalledWith('test.jpg'); expect(mockFile.save).toHaveBeenCalledWith(mockBuffer, { contentType: 'image/jpeg', metadata: { userId: '123' }, public: true, }); expect(result).toEqual({ url: 'https://storage.googleapis.com/test-bucket/test.jpg', filename: 'test.jpg', size: mockBuffer.length, format: 'jpg', service: 'gcloud', metadata: { bucket: 'test-bucket', key: 'test.jpg', projectId: 'test-project-id', }, }); }); it('should successfully upload file with folder', async () => { // Mock file doesn't exist mockFile.exists.mockResolvedValue([false]); // Mock successful upload mockFile.save.mockResolvedValue(undefined); const argsWithFolder = { ...mockArgs, folder: 'uploads/2024' }; const result = await service.upload(mockBuffer, 'test.jpg', argsWithFolder); expect(mockBucket.file).toHaveBeenCalledWith('uploads/2024/test.jpg'); expect(result.url).toBe('https://storage.googleapis.com/test-bucket/uploads/2024/test.jpg'); expect(result.metadata?.key).toBe('uploads/2024/test.jpg'); }); it('should handle file extension correctly', async () => { // Mock file doesn't exist mockFile.exists.mockResolvedValue([false]); // Mock successful upload mockFile.save.mockResolvedValue(undefined); 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 file doesn't exist mockFile.exists.mockResolvedValue([false]); // Mock successful upload mockFile.save.mockResolvedValue(undefined); 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 file doesn't exist mockFile.exists.mockResolvedValue([false]); // Mock successful upload mockFile.save.mockResolvedValue(undefined); await service.upload(mockBuffer, 'test.png', mockArgs); expect(mockFile.save).toHaveBeenCalledWith( mockBuffer, expect.objectContaining({ contentType: 'image/png', }) ); }); it('should set public flag correctly', async () => { // Mock file doesn't exist mockFile.exists.mockResolvedValue([false]); // Mock successful upload mockFile.save.mockResolvedValue(undefined); await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, public: true }); expect(mockFile.save).toHaveBeenCalledWith( mockBuffer, expect.objectContaining({ public: true, }) ); mockFile.save.mockClear(); mockFile.exists.mockResolvedValue([false]); await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, public: false }); expect(mockFile.save).toHaveBeenCalledWith( mockBuffer, expect.objectContaining({ public: false, }) ); }); it('should include metadata in upload command', async () => { // Mock file doesn't exist mockFile.exists.mockResolvedValue([false]); // Mock successful upload mockFile.save.mockResolvedValue(undefined); const metadata = { userId: '123', uploadType: 'profile' }; await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, metadata }); expect(mockFile.save).toHaveBeenCalledWith( mockBuffer, expect.objectContaining({ metadata: metadata, }) ); }); describe('overwrite protection', () => { it('should check for existing file when overwrite is false', async () => { // Mock file doesn't exist mockFile.exists.mockResolvedValue([false]); // Mock successful upload mockFile.save.mockResolvedValue(undefined); await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, overwrite: false }); expect(mockFile.exists).toHaveBeenCalled(); expect(mockFile.save).toHaveBeenCalled(); }); it('should throw error if file exists and overwrite is false', async () => { // Mock file exists mockFile.exists.mockResolvedValue([true]); 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 () => { // Mock successful upload mockFile.save.mockResolvedValue(undefined); await service.upload(mockBuffer, 'test.jpg', { ...mockArgs, overwrite: true }); expect(mockFile.exists).not.toHaveBeenCalled(); expect(mockFile.save).toHaveBeenCalled(); }); }); describe('error handling', () => { it('should wrap GCloud errors in McpError', async () => { const gcloudError = new Error('GCloud Service Error'); mockFile.exists.mockRejectedValue(gcloudError); await expect( service.upload(mockBuffer, 'test.jpg', mockArgs) ).rejects.toThrow(McpError); await expect( service.upload(mockBuffer, 'test.jpg', mockArgs) ).rejects.toThrow('GCloud upload failed: GCloud Service Error'); }); it('should preserve McpError instances', async () => { const mcpError = new McpError(ErrorCode.InvalidParams, 'Custom MCP error'); mockFile.exists.mockRejectedValue(mcpError); await expect( service.upload(mockBuffer, 'test.jpg', mockArgs) ).rejects.toThrow(mcpError); }); it('should handle upload errors', async () => { // Mock file doesn't exist mockFile.exists.mockResolvedValue([false]); // Mock upload failure const uploadError = new Error('Upload failed'); mockFile.save.mockRejectedValue(uploadError); await expect( service.upload(mockBuffer, 'test.jpg', mockArgs) ).rejects.toThrow(McpError); await expect( service.upload(mockBuffer, 'test.jpg', mockArgs) ).rejects.toThrow('GCloud upload failed: Upload failed'); }); }); }); describe('getContentType', () => { beforeEach(() => { service = new GCloudUploadService(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', () => { beforeEach(() => { service = new GCloudUploadService(mockValidatedConfig); }); it('should generate Google Cloud Storage URL', () => { const generateUrl = (service as any).generateUrl.bind(service); const url = generateUrl('test/image.jpg'); expect(url).toBe('https://storage.googleapis.com/test-bucket/test/image.jpg'); }); it('should handle keys without folders', () => { const generateUrl = (service as any).generateUrl.bind(service); const url = generateUrl('image.jpg'); expect(url).toBe('https://storage.googleapis.com/test-bucket/image.jpg'); }); }); describe('GCloud specific features', () => { it('should use Google Cloud Storage client', () => { service = new GCloudUploadService(mockValidatedConfig); expect(MockedStorage).toHaveBeenCalledWith( expect.objectContaining({ projectId: 'test-project-id', }) ); }); it('should support custom credentials path', () => { const configWithCredentials = GCloudEnvConfigSchema.parse({ GCLOUD_BUCKET: 'test-bucket', GCLOUD_PROJECT_ID: 'test-project-id', GCLOUD_CREDENTIALS_PATH: '/custom/path/credentials.json', }); service = new GCloudUploadService(configWithCredentials); expect(MockedStorage).toHaveBeenCalledWith({ projectId: 'test-project-id', keyFilename: '/custom/path/credentials.json', }); }); it('should use default authentication when no credentials path provided', () => { service = new GCloudUploadService(mockValidatedConfig); const calledWith = MockedStorage.mock.calls[0][0]; expect(calledWith).toEqual({ projectId: 'test-project-id', }); expect(calledWith).not.toHaveProperty('keyFilename'); }); }); describe('GCloudEnvConfigSchema validation', () => { it('should require GCLOUD_BUCKET', () => { expect(() => { GCloudEnvConfigSchema.parse({ GCLOUD_PROJECT_ID: 'test-project', }); }).toThrow(); }); it('should require GCLOUD_PROJECT_ID', () => { expect(() => { GCloudEnvConfigSchema.parse({ GCLOUD_BUCKET: 'test-bucket', }); }).toThrow(); }); it('should accept valid configuration', () => { const config = GCloudEnvConfigSchema.parse({ GCLOUD_BUCKET: 'test-bucket', GCLOUD_PROJECT_ID: 'test-project', }); expect(config).toEqual({ UPLOAD_SERVICE: 'gcloud', GCLOUD_BUCKET: 'test-bucket', GCLOUD_PROJECT_ID: 'test-project', }); }); it('should accept configuration with credentials path', () => { const config = GCloudEnvConfigSchema.parse({ GCLOUD_BUCKET: 'test-bucket', GCLOUD_PROJECT_ID: 'test-project', GCLOUD_CREDENTIALS_PATH: '/path/to/credentials.json', }); expect(config).toEqual({ UPLOAD_SERVICE: 'gcloud', GCLOUD_BUCKET: 'test-bucket', GCLOUD_PROJECT_ID: 'test-project', GCLOUD_CREDENTIALS_PATH: '/path/to/credentials.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/BoomLinkAi/image-worker-mcp'

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