File size: 6,837 Bytes
f0743f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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 returns the object as-is, validation is minimal
  });

  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 () => {
    // When you have \\n in JavaScript, JSON.stringify converts it to \\\\n
    // But we want to test the case where the JSON string contains \\n (single backslash + n)
    const serviceKeyWithEscapedNewlines = {
      ...mockServiceKey,
      private_key: '-----BEGIN PRIVATE KEY-----\\ntest-key\\n-----END PRIVATE KEY-----',
    };
    // This will create a JSON string where the private_key contains literal \n (backslash-n)
    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 () => {
    // This looks like base64 but isn't valid
    const invalidBase64 = 'SGVsbG8gV29ybGQ='; // "Hello World" in base64, not valid JSON

    const result = await loadServiceKey(invalidBase64);
    expect(result).toBeNull();
  });
});