import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { BadRequestException, InternalServerErrorException } from '@nestjs/common'; import { createCipheriv, createDecipheriv, randomBytes, } from 'node:crypto'; import { AiConfigService } from './ai-config.service'; import { AiConfig, AiProvider, SINGLETON_KEY } from './ai-config.entity'; // --------------------------------------------------------------------------- // Helpers for testing encryption directly // --------------------------------------------------------------------------- const ALGORITHM = 'aes-256-gcm'; const IV_LENGTH = 12; const AUTH_TAG_LENGTH = 16; function encryptWithKey(key: Buffer, plaintext: string) { const iv = randomBytes(IV_LENGTH); const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH }); const encrypted = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()]); const tag = cipher.getAuthTag(); return { ciphertext: encrypted.toString('base64'), iv: iv.toString('base64'), authTag: tag.toString('base64'), }; } function decryptWithKey( key: Buffer, ciphertextB64: string, ivB64: string, authTagB64: string, ): string { const iv = Buffer.from(ivB64, 'base64'); const authTag = Buffer.from(authTagB64, 'base64'); const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH }); decipher.setAuthTag(authTag); const decrypted = Buffer.concat([ decipher.update(Buffer.from(ciphertextB64, 'base64')), decipher.final(), ]); return decrypted.toString('utf-8'); } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- describe('AiConfigService', () => { let service: AiConfigService; let repo: jest.Mocked, 'findOne' | 'save' | 'create'>>; // Use a known encryption key so tests are deterministic const TEST_KEY_BYTES_32 = Buffer.alloc(32, 'a'); // 32 bytes of 'a' const TEST_KEY_HEX = TEST_KEY_BYTES_32.toString('hex'); // 64 hex chars function makeConfig(overrides: Partial = {}): AiConfig { return { id: 1, singletonKey: SINGLETON_KEY, provider: AiProvider.OPENAI, baseUrl: 'https://api.openai.com/v1', encryptedApiKey: null, apiKeyIv: null, apiKeyAuthTag: null, keyLast4: null, defaultModel: null, enabled: false, timeoutMs: 30000, verified: false, lastTestedAt: null, lastTestLatencyMs: null, createdAt: new Date(), updatedAt: new Date(), ...overrides, }; } beforeEach(async () => { process.env.AI_CONFIG_ENCRYPTION_KEY = TEST_KEY_HEX; delete process.env.AI_API_KEY; repo = { findOne: jest.fn(), save: jest.fn(), create: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ AiConfigService, { provide: getRepositoryToken(AiConfig), useValue: repo }, ], }).compile(); service = module.get(AiConfigService); }); afterEach(() => { delete process.env.AI_CONFIG_ENCRYPTION_KEY; delete process.env.AI_API_KEY; }); // ── Encryption ──────────────────────────────────────────────────────── describe('encryption', () => { it('roundtrip: encrypt then decrypt returns original text', () => { const plaintext = 'sk-test-key-1234567890abcdef'; const { ciphertext, iv, authTag } = encryptWithKey(TEST_KEY_BYTES_32, plaintext); const decrypted = decryptWithKey(TEST_KEY_BYTES_32, ciphertext, iv, authTag); expect(decrypted).toBe(plaintext); }); it('random IV: same key + same plaintext produces different ciphertexts', () => { const plaintext = 'sk-test-key-1234567890abcdef'; const key = TEST_KEY_BYTES_32; const r1 = encryptWithKey(key, plaintext); const r2 = encryptWithKey(key, plaintext); expect(r1.iv).not.toBe(r2.iv); expect(r1.ciphertext).not.toBe(r2.ciphertext); expect(decryptWithKey(key, r1.ciphertext, r1.iv, r1.authTag)).toBe(plaintext); expect(decryptWithKey(key, r2.ciphertext, r2.iv, r2.authTag)).toBe(plaintext); }); it('wrong key fails decryption', () => { const plaintext = 'sk-test-key-1234567890abcdef'; const correctKey = TEST_KEY_BYTES_32; const wrongKey = Buffer.alloc(32, 'b'); const { ciphertext, iv, authTag } = encryptWithKey(correctKey, plaintext); expect(() => decryptWithKey(wrongKey, ciphertext, iv, authTag), ).toThrow(); }); it('tampered auth tag fails decryption', () => { const plaintext = 'sk-test-key-1234567890abcdef'; const key = TEST_KEY_BYTES_32; const { ciphertext, iv, authTag } = encryptWithKey(key, plaintext); const tamperedTag = Buffer.from(authTag, 'base64'); tamperedTag[0] ^= 1; expect(() => decryptWithKey(key, ciphertext, iv, tamperedTag.toString('base64')), ).toThrow(); }); }); // ── Encryption key validation ───────────────────────────────────────── describe('encryption key validation', () => { it('rejects plain 32-char string (not hex or base64)', async () => { process.env.AI_CONFIG_ENCRYPTION_KEY = 'a'.repeat(32); repo.findOne.mockResolvedValue(makeConfig()); // Re-create service with new key const module: TestingModule = await Test.createTestingModule({ providers: [ AiConfigService, { provide: getRepositoryToken(AiConfig), useValue: repo }, ], }).compile(); const svc = module.get(AiConfigService); await expect( svc.saveConfig({ provider: AiProvider.OPENAI, apiKey: 'sk-test' }), ).rejects.toThrow(InternalServerErrorException); process.env.AI_CONFIG_ENCRYPTION_KEY = TEST_KEY_HEX; }); it('rejects invalid base64 input', async () => { process.env.AI_CONFIG_ENCRYPTION_KEY = '!!!invalid!!!'; repo.findOne.mockResolvedValue(makeConfig()); const module: TestingModule = await Test.createTestingModule({ providers: [ AiConfigService, { provide: getRepositoryToken(AiConfig), useValue: repo }, ], }).compile(); const svc = module.get(AiConfigService); await expect( svc.saveConfig({ provider: AiProvider.OPENAI, apiKey: 'sk-test' }), ).rejects.toThrow(InternalServerErrorException); process.env.AI_CONFIG_ENCRYPTION_KEY = TEST_KEY_HEX; }); it('accepts valid base64 32-byte key', async () => { const b64key = TEST_KEY_BYTES_32.toString('base64'); process.env.AI_CONFIG_ENCRYPTION_KEY = b64key; const existing = makeConfig(); repo.findOne.mockResolvedValue(existing); repo.save.mockImplementation((c) => Promise.resolve(c)); const module: TestingModule = await Test.createTestingModule({ providers: [ AiConfigService, { provide: getRepositoryToken(AiConfig), useValue: repo }, ], }).compile(); const svc = module.get(AiConfigService); const result = await svc.saveConfig({ provider: AiProvider.OPENAI }); expect(result).toBeDefined(); process.env.AI_CONFIG_ENCRYPTION_KEY = TEST_KEY_HEX; }); it('rejects non-canonical base64 (extra padding)', async () => { const canonicalB64 = TEST_KEY_BYTES_32.toString('base64'); // Non-canonical: add extra padding const badB64 = canonicalB64 + '=='; process.env.AI_CONFIG_ENCRYPTION_KEY = badB64; repo.findOne.mockResolvedValue(makeConfig()); const module: TestingModule = await Test.createTestingModule({ providers: [ AiConfigService, { provide: getRepositoryToken(AiConfig), useValue: repo }, ], }).compile(); const svc = module.get(AiConfigService); await expect( svc.saveConfig({ provider: AiProvider.OPENAI, apiKey: 'sk-test' }), ).rejects.toThrow(InternalServerErrorException); process.env.AI_CONFIG_ENCRYPTION_KEY = TEST_KEY_HEX; }); it('rejects base64 with invalid characters', async () => { process.env.AI_CONFIG_ENCRYPTION_KEY = '!!!!aaaa'; repo.findOne.mockResolvedValue(makeConfig()); const module: TestingModule = await Test.createTestingModule({ providers: [ AiConfigService, { provide: getRepositoryToken(AiConfig), useValue: repo }, ], }).compile(); const svc = module.get(AiConfigService); await expect( svc.saveConfig({ provider: AiProvider.OPENAI, apiKey: 'sk-test' }), ).rejects.toThrow(InternalServerErrorException); process.env.AI_CONFIG_ENCRYPTION_KEY = TEST_KEY_HEX; }); it('throws in production when no key set', async () => { process.env.NODE_ENV = 'production'; delete process.env.AI_CONFIG_ENCRYPTION_KEY; repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI, apiKey: 'sk-test' }), ).rejects.toThrow(InternalServerErrorException); delete process.env.NODE_ENV; process.env.AI_CONFIG_ENCRYPTION_KEY = TEST_KEY_HEX; }); }); // ── Config management ───────────────────────────────────────────────── describe('getOrCreateConfig', () => { it('returns existing config when found', async () => { const existing = makeConfig(); repo.findOne.mockResolvedValue(existing); const result = await service.getOrCreateConfig(); expect(result).toBe(existing); expect(repo.findOne).toHaveBeenCalledWith({ where: { singletonKey: SINGLETON_KEY } }); expect(repo.create).not.toHaveBeenCalled(); }); it('creates default config when none exists', async () => { repo.findOne.mockResolvedValue(null); const created = makeConfig(); repo.create.mockReturnValue(created); repo.save.mockResolvedValue(created); const result = await service.getOrCreateConfig(); expect(repo.create).toHaveBeenCalled(); expect(result.provider).toBe(AiProvider.OPENAI); expect(result.enabled).toBe(false); }); }); describe('getConfig', () => { it('returns masked key info with source=none when no key', async () => { repo.findOne.mockResolvedValue(makeConfig()); const result = await service.getConfig(); expect(result.hasApiKey).toBe(false); expect(result.hasDatabaseKey).toBe(false); expect(result.maskedApiKey).toBeNull(); expect(result.keySource).toBe('none'); }); it('returns hasApiKey=true and hasDatabaseKey=true when DB key exists', async () => { const key = 'sk-abcdefghij1234'; const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, key, ); repo.findOne.mockResolvedValue( makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag, keyLast4: '1234', }), ); const result = await service.getConfig(); expect(result.hasApiKey).toBe(true); expect(result.hasDatabaseKey).toBe(true); expect(result.maskedApiKey).toBe('••••1234'); expect(result.keySource).toBe('database'); }); it('never returns plaintext key in GET response', async () => { const key = 'sk-topsecret1234'; const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, key, ); repo.findOne.mockResolvedValue( makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag, keyLast4: '1234', }), ); const result = await service.getConfig(); const json = JSON.stringify(result); expect(json).not.toContain('topsecret'); expect(json).not.toContain('sk-'); }); it('env key fallback: source=environment, hasDatabaseKey=false', async () => { process.env.AI_API_KEY = 'sk-env-key-1234'; repo.findOne.mockResolvedValue(makeConfig()); const result = await service.getConfig(); expect(result.hasApiKey).toBe(true); expect(result.hasDatabaseKey).toBe(false); expect(result.keySource).toBe('environment'); delete process.env.AI_API_KEY; }); }); // ── Save config ─────────────────────────────────────────────────────── describe('saveConfig', () => { it('saves provider and baseUrl', async () => { const existing = makeConfig(); repo.findOne.mockResolvedValue(existing); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.saveConfig({ provider: AiProvider.DEEPSEEK, baseUrl: 'https://api.deepseek.com', }); expect(result.provider).toBe(AiProvider.DEEPSEEK); expect(result.baseUrl).toBe('https://api.deepseek.com'); }); it('encrypts and saves apiKey', async () => { const existing = makeConfig(); repo.findOne.mockResolvedValue(existing); repo.save.mockImplementation((c) => Promise.resolve(c)); const apiKey = 'sk-saved-key-5678'; const result = await service.saveConfig({ provider: AiProvider.OPENAI, apiKey, }); expect(result.encryptedApiKey).toBeTruthy(); expect(result.apiKeyIv).toBeTruthy(); expect(result.apiKeyAuthTag).toBeTruthy(); expect(result.keyLast4).toBe('5678'); const encKey = result.encryptedApiKey; const encIv = result.apiKeyIv; const encTag = result.apiKeyAuthTag; expect(encKey).toBeTruthy(); expect(encIv).toBeTruthy(); expect(encTag).toBeTruthy(); if (!encKey || !encIv || !encTag) throw new Error('encrypted fields missing'); const decrypted = decryptWithKey(TEST_KEY_BYTES_32, encKey, encIv, encTag); expect(decrypted).toBe(apiKey); }); it('empty apiKey preserves existing key', async () => { const existingKey = 'sk-existing-9999'; const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, existingKey, ); const existing = makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag, keyLast4: '9999', }); repo.findOne.mockResolvedValue(existing); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.saveConfig({ provider: AiProvider.OPENAI, apiKey: '', }); expect(result.encryptedApiKey).toBe(ciphertext); expect(result.keyLast4).toBe('9999'); }); it('undefined apiKey preserves existing key', async () => { const existingKey = 'sk-existing-9999'; const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, existingKey, ); const existing = makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag, keyLast4: '9999', }); repo.findOne.mockResolvedValue(existing); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.saveConfig({ provider: AiProvider.OPENAI, }); expect(result.encryptedApiKey).toBe(ciphertext); }); it('rejects enabled=true without any key', async () => { repo.findOne.mockResolvedValue(makeConfig()); repo.save.mockImplementation((c) => Promise.resolve(c)); await expect( service.saveConfig({ provider: AiProvider.OPENAI, enabled: true, }), ).rejects.toThrow(BadRequestException); }); it('allows enabled=true when DB key exists and defaultModel is set', async () => { const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, 'sk-existing-key', ); repo.findOne.mockResolvedValue( makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag, defaultModel: 'gpt-4' }), ); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.saveConfig({ provider: AiProvider.OPENAI, enabled: true, }); expect(result.enabled).toBe(true); }); it('allows enabled=true with env key fallback and defaultModel', async () => { process.env.AI_API_KEY = 'sk-env-key'; repo.findOne.mockResolvedValue(makeConfig({ defaultModel: 'gpt-4' })); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.saveConfig({ provider: AiProvider.OPENAI, enabled: true, }); expect(result.enabled).toBe(true); delete process.env.AI_API_KEY; }); it('provider switch replaces default baseUrl', async () => { repo.findOne.mockResolvedValue(makeConfig({ baseUrl: 'https://api.openai.com/v1' })); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.saveConfig({ provider: AiProvider.DEEPSEEK, }); expect(result.baseUrl).toBe('https://api.deepseek.com'); }); it('OPENAI_COMPATIBLE requires baseUrl', async () => { repo.findOne.mockResolvedValue(makeConfig()); repo.save.mockImplementation((c) => Promise.resolve(c)); await expect( service.saveConfig({ provider: AiProvider.OPENAI_COMPATIBLE, }), ).rejects.toThrow(BadRequestException); }); it('enabled=true with defaultModel in DTO works even if config has none', async () => { const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, 'sk-existing-key', ); repo.findOne.mockResolvedValue( makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag }), ); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.saveConfig({ provider: AiProvider.OPENAI, enabled: true, defaultModel: 'gpt-4', }); expect(result.enabled).toBe(true); }); it('enabled=true rejects when defaultModel is empty everywhere', async () => { const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, 'sk-existing-key', ); repo.findOne.mockResolvedValue( makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag }), ); repo.save.mockImplementation((c) => Promise.resolve(c)); await expect( service.saveConfig({ provider: AiProvider.OPENAI, enabled: true, }), ).rejects.toThrow(BadRequestException); }); }); // ── Clear key ───────────────────────────────────────────────────────── describe('clearKey', () => { it('clears DB key and disables when no env key', async () => { const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, 'sk-to-clear', ); repo.findOne.mockResolvedValue( makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag, keyLast4: 'lear', enabled: true, }), ); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.clearKey(); expect(result.hasApiKey).toBe(false); expect(result.hasDatabaseKey).toBe(false); expect(result.keySource).toBe('none'); expect(result.maskedApiKey).toBeNull(); }); it('clear DB key falls back to env source', async () => { process.env.AI_API_KEY = 'sk-env-after-clear'; const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, 'sk-to-clear', ); repo.findOne.mockResolvedValue( makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag, keyLast4: 'lear', }), ); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.clearKey(); expect(result.keySource).toBe('environment'); expect(result.hasDatabaseKey).toBe(false); delete process.env.AI_API_KEY; }); }); // ── URL / SSRF validation ────────────────────────────────────────────── describe('baseUrl validation', () => { it('rejects non-http protocol', async () => { repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI_COMPATIBLE, baseUrl: 'ftp://evil.com', }), ).rejects.toThrow(BadRequestException); }); it('rejects URL with username/password', async () => { repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI_COMPATIBLE, baseUrl: 'https://user:pass@evil.com', }), ).rejects.toThrow(BadRequestException); }); it('rejects URL with search/query string', async () => { repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI_COMPATIBLE, baseUrl: 'https://evil.com/v1?proxy=internal', }), ).rejects.toThrow(BadRequestException); }); it('rejects URL with hash/fragment', async () => { repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI_COMPATIBLE, baseUrl: 'https://evil.com/v1#section', }), ).rejects.toThrow(BadRequestException); }); it('rejects localhost for OPENAI_COMPATIBLE', async () => { repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI_COMPATIBLE, baseUrl: 'http://localhost:8080/v1', }), ).rejects.toThrow(BadRequestException); }); it('rejects 127.0.0.1 for OPENAI_COMPATIBLE', async () => { repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI_COMPATIBLE, baseUrl: 'https://127.0.0.1:8080', }), ).rejects.toThrow(BadRequestException); }); it('rejects .local hostname', async () => { repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI_COMPATIBLE, baseUrl: 'https://myservice.local/v1', }), ).rejects.toThrow(BadRequestException); }); it('rejects IPv6 loopback ::1', async () => { repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI_COMPATIBLE, baseUrl: 'http://[::1]:8080/v1', }), ).rejects.toThrow(BadRequestException); }); it('allows private IP when AI_ALLOW_PRIVATE_BASE_URL=true', async () => { process.env.AI_ALLOW_PRIVATE_BASE_URL = 'true'; repo.findOne.mockResolvedValue(makeConfig()); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.saveConfig({ provider: AiProvider.OPENAI_COMPATIBLE, baseUrl: 'http://localhost:8080/v1', }); expect(result.baseUrl).toBe('http://localhost:8080/v1'); delete process.env.AI_ALLOW_PRIVATE_BASE_URL; }); it('OPENAI rejects non-openai hostname', async () => { repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI, baseUrl: 'https://evil.com/v1', }), ).rejects.toThrow(BadRequestException); }); it('OPENAI rejects wrong pathname', async () => { repo.findOne.mockResolvedValue(makeConfig()); await expect( service.saveConfig({ provider: AiProvider.OPENAI, baseUrl: 'https://api.openai.com/evil-proxy', }), ).rejects.toThrow(BadRequestException); }); it('normalizes trailing slash', async () => { repo.findOne.mockResolvedValue(makeConfig()); repo.save.mockImplementation((c) => Promise.resolve(c)); const result = await service.saveConfig({ provider: AiProvider.OPENAI, baseUrl: 'https://api.openai.com/v1/', }); expect(result.baseUrl).toBe('https://api.openai.com/v1'); }); }); // ── getRuntimeConfig ────────────────────────────────────────────────── describe('getRuntimeConfig', () => { it('returns config with plaintext key', async () => { const apiKey = 'sk-runtime-key'; const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, apiKey, ); repo.findOne.mockResolvedValue( makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag, enabled: true, defaultModel: 'gpt-4', }), ); const runtime = await service.getRuntimeConfig(); expect(runtime.apiKey).toBe(apiKey); expect(runtime.enabled).toBe(true); }); it('throws when not enabled', async () => { repo.findOne.mockResolvedValue(makeConfig({ enabled: false })); await expect(service.getRuntimeConfig()).rejects.toThrow(BadRequestException); }); it('throws when no key available', async () => { repo.findOne.mockResolvedValue(makeConfig({ enabled: true })); await expect(service.getRuntimeConfig()).rejects.toThrow(BadRequestException); }); it('throws when defaultModel is empty', async () => { const { ciphertext, iv, authTag } = encryptWithKey( TEST_KEY_BYTES_32, 'sk-runtime-key', ); repo.findOne.mockResolvedValue( makeConfig({ encryptedApiKey: ciphertext, apiKeyIv: iv, apiKeyAuthTag: authTag, enabled: true, defaultModel: null, }), ); await expect(service.getRuntimeConfig()).rejects.toThrow(BadRequestException); }); it('throws when no config row exists', async () => { repo.findOne.mockResolvedValue(null); await expect(service.getRuntimeConfig()).rejects.toThrow(InternalServerErrorException); }); }); });