| import path from 'path'; |
| import axios from 'axios'; |
| import { readFileAsString } from './files'; |
| import { loadServiceKey } from './key'; |
|
|
| jest.mock('fs'); |
| jest.mock('axios'); |
| jest.mock('@librechat/data-schemas', () => ({ |
| logger: { |
| error: jest.fn(), |
| }, |
| })); |
|
|
| jest.mock('./files', () => ({ |
| readFileAsString: jest.fn(), |
| })); |
|
|
| describe('loadServiceKey', () => { |
| const mockServiceKey = { |
| type: 'service_account', |
| project_id: 'test-project', |
| private_key_id: 'test-key-id', |
| private_key: '-----BEGIN PRIVATE KEY-----\ntest-key\n-----END PRIVATE KEY-----', |
| client_email: 'test@test-project.iam.gserviceaccount.com', |
| client_id: '123456789', |
| auth_uri: 'https://accounts.google.com/o/oauth2/auth', |
| token_uri: 'https://oauth2.googleapis.com/token', |
| auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs', |
| client_x509_cert_url: |
| 'https://www.googleapis.com/robot/v1/metadata/x509/test%40test-project.iam.gserviceaccount.com', |
| }; |
|
|
| beforeEach(() => { |
| jest.clearAllMocks(); |
| }); |
|
|
| it('should return null if keyPath is empty', async () => { |
| const result = await loadServiceKey(''); |
| expect(result).toBeNull(); |
| }); |
|
|
| it('should parse stringified JSON directly', async () => { |
| const jsonString = JSON.stringify(mockServiceKey); |
| const result = await loadServiceKey(jsonString); |
| expect(result).toEqual(mockServiceKey); |
| }); |
|
|
| it('should parse stringified JSON with leading/trailing whitespace', async () => { |
| const jsonString = ` ${JSON.stringify(mockServiceKey)} `; |
| const result = await loadServiceKey(jsonString); |
| expect(result).toEqual(mockServiceKey); |
| }); |
|
|
| it('should load from file path', async () => { |
| const filePath = '/path/to/service-key.json'; |
| (readFileAsString as jest.Mock).mockResolvedValue({ |
| content: JSON.stringify(mockServiceKey), |
| bytes: JSON.stringify(mockServiceKey).length, |
| }); |
|
|
| const result = await loadServiceKey(filePath); |
| expect(readFileAsString).toHaveBeenCalledWith(path.resolve(filePath)); |
| expect(result).toEqual(mockServiceKey); |
| }); |
|
|
| it('should load from URL', async () => { |
| const url = 'https://example.com/service-key.json'; |
| (axios.get as jest.Mock).mockResolvedValue({ data: mockServiceKey }); |
|
|
| const result = await loadServiceKey(url); |
| expect(axios.get).toHaveBeenCalledWith(url); |
| expect(result).toEqual(mockServiceKey); |
| }); |
|
|
| it('should handle invalid JSON string', async () => { |
| const invalidJson = '{ invalid json }'; |
| const result = await loadServiceKey(invalidJson); |
| expect(result).toBeNull(); |
| }); |
|
|
| it('should handle file read errors', async () => { |
| const filePath = '/path/to/nonexistent.json'; |
| (readFileAsString as jest.Mock).mockRejectedValue(new Error('File not found')); |
|
|
| const result = await loadServiceKey(filePath); |
| expect(result).toBeNull(); |
| }); |
|
|
| it('should handle URL fetch errors', async () => { |
| const url = 'https://example.com/service-key.json'; |
| (axios.get as jest.Mock).mockRejectedValue(new Error('Network error')); |
|
|
| const result = await loadServiceKey(url); |
| expect(result).toBeNull(); |
| }); |
|
|
| it('should validate service key format', async () => { |
| const invalidServiceKey = { invalid: 'key' }; |
| const result = await loadServiceKey(JSON.stringify(invalidServiceKey)); |
| expect(result).toEqual(invalidServiceKey); |
| }); |
|
|
| it('should handle escaped newlines in private key from AWS Secrets Manager', async () => { |
| const serviceKeyWithEscapedNewlines = { |
| ...mockServiceKey, |
| private_key: '-----BEGIN PRIVATE KEY-----\\ntest-key\\n-----END PRIVATE KEY-----', |
| }; |
| const jsonString = JSON.stringify(serviceKeyWithEscapedNewlines); |
|
|
| const result = await loadServiceKey(jsonString); |
| expect(result).not.toBeNull(); |
| expect(result?.private_key).toBe( |
| '-----BEGIN PRIVATE KEY-----\ntest-key\n-----END PRIVATE KEY-----', |
| ); |
| }); |
|
|
| it('should handle double-escaped newlines in private key', async () => { |
| |
| |
| const serviceKeyWithEscapedNewlines = { |
| ...mockServiceKey, |
| private_key: '-----BEGIN PRIVATE KEY-----\\ntest-key\\n-----END PRIVATE KEY-----', |
| }; |
| |
| const jsonString = JSON.stringify(serviceKeyWithEscapedNewlines); |
|
|
| const result = await loadServiceKey(jsonString); |
| expect(result).not.toBeNull(); |
| expect(result?.private_key).toBe( |
| '-----BEGIN PRIVATE KEY-----\ntest-key\n-----END PRIVATE KEY-----', |
| ); |
| }); |
|
|
| it('should handle private key without any newlines', async () => { |
| const serviceKeyWithoutNewlines = { |
| ...mockServiceKey, |
| private_key: '-----BEGIN PRIVATE KEY-----test-key-----END PRIVATE KEY-----', |
| }; |
| const jsonString = JSON.stringify(serviceKeyWithoutNewlines); |
|
|
| const result = await loadServiceKey(jsonString); |
| expect(result).not.toBeNull(); |
| expect(result?.private_key).toBe( |
| '-----BEGIN PRIVATE KEY-----\ntest-key\n-----END PRIVATE KEY-----', |
| ); |
| }); |
|
|
| it('should not modify private key that already has proper formatting', async () => { |
| const jsonString = JSON.stringify(mockServiceKey); |
|
|
| const result = await loadServiceKey(jsonString); |
| expect(result).not.toBeNull(); |
| expect(result?.private_key).toBe(mockServiceKey.private_key); |
| }); |
|
|
| it('should handle base64 encoded service key', async () => { |
| const jsonString = JSON.stringify(mockServiceKey); |
| const base64Encoded = Buffer.from(jsonString).toString('base64'); |
|
|
| const result = await loadServiceKey(base64Encoded); |
| expect(result).not.toBeNull(); |
| expect(result).toEqual(mockServiceKey); |
| }); |
|
|
| it('should handle base64 encoded service key with escaped newlines', async () => { |
| const serviceKeyWithEscapedNewlines = { |
| ...mockServiceKey, |
| private_key: '-----BEGIN PRIVATE KEY-----\\ntest-key\\n-----END PRIVATE KEY-----', |
| }; |
| const jsonString = JSON.stringify(serviceKeyWithEscapedNewlines); |
| const base64Encoded = Buffer.from(jsonString).toString('base64'); |
|
|
| const result = await loadServiceKey(base64Encoded); |
| expect(result).not.toBeNull(); |
| expect(result?.private_key).toBe( |
| '-----BEGIN PRIVATE KEY-----\ntest-key\n-----END PRIVATE KEY-----', |
| ); |
| }); |
|
|
| it('should handle invalid base64 strings gracefully', async () => { |
| |
| const invalidBase64 = 'SGVsbG8gV29ybGQ='; |
|
|
| const result = await loadServiceKey(invalidBase64); |
| expect(result).toBeNull(); |
| }); |
| }); |
|
|