mirror of https://github.com/buster-so/buster.git
fix all tests and stuffc
This commit is contained in:
parent
93e80f5698
commit
40a612f10a
|
@ -1,27 +1,30 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createProxiedResponse } from './electricHandler';
|
||||
|
||||
// Mock @buster/secrets
|
||||
vi.mock('@buster/secrets', () => ({
|
||||
getSecret: vi.fn(),
|
||||
ELECTRIC_KEYS: {
|
||||
ELECTRIC_PROXY_URL: 'ELECTRIC_PROXY_URL',
|
||||
ELECTRIC_SECRET: 'ELECTRIC_SECRET',
|
||||
ELECTRIC_SOURCE_ID: 'ELECTRIC_SOURCE_ID',
|
||||
},
|
||||
}));
|
||||
|
||||
import { getSecret } from '@buster/secrets';
|
||||
|
||||
describe('createProxiedResponse', () => {
|
||||
const mockFetch = vi.fn<typeof fetch>();
|
||||
let originalEnv: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('fetch', mockFetch);
|
||||
vi.clearAllMocks();
|
||||
// Store original environment variable
|
||||
originalEnv = process.env.ELECTRIC_SECRET;
|
||||
// Set default secret for tests
|
||||
process.env.ELECTRIC_SECRET = 'test-secret';
|
||||
// Default mock for getSecret - can be overridden in individual tests
|
||||
vi.mocked(getSecret).mockResolvedValue('test-secret');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
// Restore original environment variable
|
||||
if (originalEnv !== undefined) {
|
||||
process.env.ELECTRIC_SECRET = originalEnv;
|
||||
} else {
|
||||
process.env.ELECTRIC_SECRET = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
it('should proxy a successful response and remove content-encoding and content-length headers', async () => {
|
||||
|
@ -213,8 +216,8 @@ describe('createProxiedResponse', () => {
|
|||
});
|
||||
|
||||
it('should throw error when ELECTRIC_SECRET environment variable is not set', async () => {
|
||||
// Remove the environment variable
|
||||
process.env.ELECTRIC_SECRET = '';
|
||||
// Mock getSecret to return empty string for ELECTRIC_SECRET
|
||||
vi.mocked(getSecret).mockResolvedValue('');
|
||||
|
||||
const testUrl = new URL('https://example.com/test');
|
||||
|
||||
|
@ -226,7 +229,7 @@ describe('createProxiedResponse', () => {
|
|||
|
||||
it('should add secret key to URL params when ELECTRIC_SECRET is set', async () => {
|
||||
const secretKey = 'test-secret-key-123';
|
||||
process.env.ELECTRIC_SECRET = secretKey;
|
||||
vi.mocked(getSecret).mockResolvedValue(secretKey);
|
||||
|
||||
const testUrl = new URL('https://example.com/test');
|
||||
const mockResponseBody = 'test response';
|
||||
|
|
|
@ -1,33 +1,31 @@
|
|||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { extractParamFromWhere, getElectricShapeUrl } from '.';
|
||||
|
||||
// Mock @buster/secrets
|
||||
vi.mock('@buster/secrets', () => ({
|
||||
getSecret: vi.fn(),
|
||||
ELECTRIC_KEYS: {
|
||||
ELECTRIC_PROXY_URL: 'ELECTRIC_PROXY_URL',
|
||||
ELECTRIC_SECRET: 'ELECTRIC_SECRET',
|
||||
ELECTRIC_SOURCE_ID: 'ELECTRIC_SOURCE_ID',
|
||||
},
|
||||
}));
|
||||
|
||||
import { getSecret } from '@buster/secrets';
|
||||
|
||||
describe('getElectricShapeUrl', () => {
|
||||
let originalElectricUrl: string | undefined;
|
||||
let originalSourceId: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
// Save original environment variables
|
||||
originalElectricUrl = process.env.ELECTRIC_PROXY_URL;
|
||||
originalSourceId = process.env.ELECTRIC_SOURCE_ID;
|
||||
|
||||
// Set default test values
|
||||
process.env.ELECTRIC_PROXY_URL = 'http://localhost:3000';
|
||||
process.env.ELECTRIC_SOURCE_ID = '';
|
||||
vi.clearAllMocks();
|
||||
// Default mock for getSecret
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'ELECTRIC_PROXY_URL') return 'http://localhost:3000';
|
||||
if (key === 'ELECTRIC_SOURCE_ID') return '';
|
||||
throw new Error(`Secret not found: ${key}`);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original environment variables
|
||||
if (originalElectricUrl !== undefined) {
|
||||
process.env.ELECTRIC_PROXY_URL = originalElectricUrl;
|
||||
} else {
|
||||
delete process.env.ELECTRIC_PROXY_URL;
|
||||
}
|
||||
|
||||
if (originalSourceId !== undefined) {
|
||||
process.env.ELECTRIC_SOURCE_ID = originalSourceId;
|
||||
} else {
|
||||
delete process.env.ELECTRIC_SOURCE_ID;
|
||||
}
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should return default URL with /v1/shape path when no ELECTRIC_PROXY_URL is set', async () => {
|
||||
|
@ -38,7 +36,11 @@ describe('getElectricShapeUrl', () => {
|
|||
});
|
||||
|
||||
it('should use ELECTRIC_PROXY_URL environment variable when set', async () => {
|
||||
process.env.ELECTRIC_PROXY_URL = 'https://electric.example.com';
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'ELECTRIC_PROXY_URL') return 'https://electric.example.com';
|
||||
if (key === 'ELECTRIC_SOURCE_ID') return '';
|
||||
throw new Error(`Secret not found: ${key}`);
|
||||
});
|
||||
const requestUrl = 'http://example.com/test?table=users&live=true';
|
||||
const result = await getElectricShapeUrl(requestUrl);
|
||||
|
||||
|
@ -169,7 +171,11 @@ describe('getElectricShapeUrl', () => {
|
|||
});
|
||||
|
||||
it('should handle ELECTRIC_PROXY_URL with trailing slash', async () => {
|
||||
process.env.ELECTRIC_PROXY_URL = 'https://electric.example.com/';
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'ELECTRIC_PROXY_URL') return 'https://electric.example.com/';
|
||||
if (key === 'ELECTRIC_SOURCE_ID') return '';
|
||||
throw new Error(`Secret not found: ${key}`);
|
||||
});
|
||||
const requestUrl = 'http://example.com/test?table=users';
|
||||
const result = await getElectricShapeUrl(requestUrl);
|
||||
|
||||
|
@ -177,7 +183,11 @@ describe('getElectricShapeUrl', () => {
|
|||
});
|
||||
|
||||
it('should handle ELECTRIC_PROXY_URL with path', async () => {
|
||||
process.env.ELECTRIC_PROXY_URL = 'https://api.example.com/electric';
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'ELECTRIC_PROXY_URL') return 'https://api.example.com/electric';
|
||||
if (key === 'ELECTRIC_SOURCE_ID') return '';
|
||||
throw new Error(`Secret not found: ${key}`);
|
||||
});
|
||||
const requestUrl = 'http://example.com/test?table=users';
|
||||
const result = await getElectricShapeUrl(requestUrl);
|
||||
|
||||
|
|
|
@ -2,144 +2,180 @@ import { App } from 'octokit';
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createGitHubApp, getGitHubAppCredentials } from './github-app';
|
||||
|
||||
// Mock @buster/secrets
|
||||
vi.mock('@buster/secrets', () => ({
|
||||
getSecret: vi.fn(),
|
||||
GITHUB_KEYS: {
|
||||
GITHUB_APP_ID: 'GITHUB_APP_ID',
|
||||
GITHUB_APP_NAME: 'GITHUB_APP_NAME',
|
||||
GITHUB_APP_PRIVATE_KEY_BASE64: 'GITHUB_APP_PRIVATE_KEY_BASE64',
|
||||
GITHUB_APP_PRIVATE_KEY_BASE: 'GITHUB_APP_PRIVATE_KEY_BASE',
|
||||
GITHUB_WEBHOOK_SECRET: 'GITHUB_WEBHOOK_SECRET',
|
||||
GITHUB_TOKEN: 'GITHUB_TOKEN',
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock the octokit module
|
||||
vi.mock('octokit', () => ({
|
||||
App: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('github-app', () => {
|
||||
const originalEnv = process.env;
|
||||
import { getSecret } from '@buster/secrets';
|
||||
|
||||
describe('github-app', () => {
|
||||
beforeEach(() => {
|
||||
// Reset environment variables
|
||||
process.env = { ...originalEnv };
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original environment
|
||||
process.env = originalEnv;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('getGitHubAppCredentials', () => {
|
||||
it('should return credentials when all environment variables are set', () => {
|
||||
it('should return credentials when all environment variables are set', async () => {
|
||||
// Arrange
|
||||
process.env.GITHUB_APP_ID = '123456';
|
||||
process.env.GITHUB_APP_PRIVATE_KEY_BASE64 = Buffer.from(
|
||||
'-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----'
|
||||
).toString('base64');
|
||||
process.env.GITHUB_WEBHOOK_SECRET = 'webhook-secret';
|
||||
const privateKey = '-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----';
|
||||
const privateKeyBase64 = Buffer.from(privateKey).toString('base64');
|
||||
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'GITHUB_APP_ID') return '123456';
|
||||
if (key === 'GITHUB_APP_PRIVATE_KEY_BASE64') return privateKeyBase64;
|
||||
if (key === 'GITHUB_WEBHOOK_SECRET') return 'webhook-secret';
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
});
|
||||
|
||||
// Act
|
||||
const credentials = getGitHubAppCredentials();
|
||||
const credentials = await getGitHubAppCredentials();
|
||||
|
||||
// Assert
|
||||
expect(credentials).toEqual({
|
||||
appId: 123456,
|
||||
privateKey: '-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----',
|
||||
privateKey: privateKey,
|
||||
webhookSecret: 'webhook-secret',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when GITHUB_APP_ID is missing', () => {
|
||||
it('should throw error when GITHUB_APP_ID is missing', async () => {
|
||||
// Arrange
|
||||
delete process.env.GITHUB_APP_ID;
|
||||
process.env.GITHUB_APP_PRIVATE_KEY_BASE64 = 'test';
|
||||
process.env.GITHUB_WEBHOOK_SECRET = 'test';
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'GITHUB_APP_ID') return '';
|
||||
if (key === 'GITHUB_APP_PRIVATE_KEY_BASE64') return 'test';
|
||||
if (key === 'GITHUB_WEBHOOK_SECRET') return 'test';
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
expect(() => getGitHubAppCredentials()).toThrow(
|
||||
await expect(getGitHubAppCredentials()).rejects.toThrow(
|
||||
'GITHUB_APP_ID environment variable is not set'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when GITHUB_APP_PRIVATE_KEY_BASE64 is missing', () => {
|
||||
it('should throw error when GITHUB_APP_PRIVATE_KEY_BASE64 is missing', async () => {
|
||||
// Arrange
|
||||
process.env.GITHUB_APP_ID = '123456';
|
||||
delete process.env.GITHUB_APP_PRIVATE_KEY_BASE64;
|
||||
process.env.GITHUB_WEBHOOK_SECRET = 'test';
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'GITHUB_APP_ID') return '123456';
|
||||
if (key === 'GITHUB_APP_PRIVATE_KEY_BASE64') return '';
|
||||
if (key === 'GITHUB_WEBHOOK_SECRET') return 'test';
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
expect(() => getGitHubAppCredentials()).toThrow(
|
||||
await expect(getGitHubAppCredentials()).rejects.toThrow(
|
||||
'GITHUB_APP_PRIVATE_KEY_BASE64 environment variable is not set'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when GITHUB_WEBHOOK_SECRET is missing', () => {
|
||||
it('should throw error when GITHUB_WEBHOOK_SECRET is missing', async () => {
|
||||
// Arrange
|
||||
process.env.GITHUB_APP_ID = '123456';
|
||||
process.env.GITHUB_APP_PRIVATE_KEY_BASE64 = 'test';
|
||||
delete process.env.GITHUB_WEBHOOK_SECRET;
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'GITHUB_APP_ID') return '123456';
|
||||
if (key === 'GITHUB_APP_PRIVATE_KEY_BASE64') return 'test';
|
||||
if (key === 'GITHUB_WEBHOOK_SECRET') return '';
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
expect(() => getGitHubAppCredentials()).toThrow(
|
||||
await expect(getGitHubAppCredentials()).rejects.toThrow(
|
||||
'GITHUB_WEBHOOK_SECRET environment variable is not set'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when private key base64 is invalid', () => {
|
||||
it('should throw error when private key base64 is invalid', async () => {
|
||||
// Arrange
|
||||
process.env.GITHUB_APP_ID = '123456';
|
||||
process.env.GITHUB_APP_PRIVATE_KEY_BASE64 = 'not-valid-base64!@#$%';
|
||||
process.env.GITHUB_WEBHOOK_SECRET = 'test';
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'GITHUB_APP_ID') return '123456';
|
||||
if (key === 'GITHUB_APP_PRIVATE_KEY_BASE64') return 'not-valid-base64!@#$%';
|
||||
if (key === 'GITHUB_WEBHOOK_SECRET') return 'test';
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
expect(() => getGitHubAppCredentials()).toThrow(
|
||||
await expect(getGitHubAppCredentials()).rejects.toThrow(
|
||||
'Failed to decode GITHUB_APP_PRIVATE_KEY_BASE64: Invalid base64 encoding'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when private key format is invalid', () => {
|
||||
it('should throw error when private key format is invalid', async () => {
|
||||
// Arrange
|
||||
process.env.GITHUB_APP_ID = '123456';
|
||||
process.env.GITHUB_APP_PRIVATE_KEY_BASE64 =
|
||||
Buffer.from('not-a-private-key').toString('base64');
|
||||
process.env.GITHUB_WEBHOOK_SECRET = 'test';
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'GITHUB_APP_ID') return '123456';
|
||||
if (key === 'GITHUB_APP_PRIVATE_KEY_BASE64') return Buffer.from('not-a-private-key').toString('base64');
|
||||
if (key === 'GITHUB_WEBHOOK_SECRET') return 'test';
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
expect(() => getGitHubAppCredentials()).toThrow(
|
||||
await expect(getGitHubAppCredentials()).rejects.toThrow(
|
||||
'Invalid GitHub App private key format. Expected PEM-encoded RSA private key or PKCS#8 private key'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGitHubApp', () => {
|
||||
it('should create GitHub App with valid credentials', () => {
|
||||
it('should create GitHub App with valid credentials', async () => {
|
||||
// Arrange
|
||||
process.env.GITHUB_APP_ID = '123456';
|
||||
process.env.GITHUB_APP_PRIVATE_KEY_BASE64 = Buffer.from(
|
||||
'-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----'
|
||||
).toString('base64');
|
||||
process.env.GITHUB_WEBHOOK_SECRET = 'webhook-secret';
|
||||
const privateKey = '-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----';
|
||||
const privateKeyBase64 = Buffer.from(privateKey).toString('base64');
|
||||
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'GITHUB_APP_ID') return '123456';
|
||||
if (key === 'GITHUB_APP_PRIVATE_KEY_BASE64') return privateKeyBase64;
|
||||
if (key === 'GITHUB_WEBHOOK_SECRET') return 'webhook-secret';
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
});
|
||||
|
||||
const mockApp = { octokit: {} };
|
||||
(App as any).mockImplementation(() => mockApp);
|
||||
|
||||
// Act
|
||||
const app = createGitHubApp();
|
||||
const app = await createGitHubApp();
|
||||
|
||||
// Assert
|
||||
expect(App).toHaveBeenCalledWith({
|
||||
appId: 123456,
|
||||
privateKey: '-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----',
|
||||
privateKey: privateKey,
|
||||
});
|
||||
expect(app).toBe(mockApp);
|
||||
});
|
||||
|
||||
it('should throw error when App creation fails', () => {
|
||||
it('should throw error when App creation fails', async () => {
|
||||
// Arrange
|
||||
process.env.GITHUB_APP_ID = '123456';
|
||||
process.env.GITHUB_APP_PRIVATE_KEY_BASE64 = Buffer.from(
|
||||
'-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----'
|
||||
).toString('base64');
|
||||
process.env.GITHUB_WEBHOOK_SECRET = 'webhook-secret';
|
||||
const privateKey = '-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----';
|
||||
const privateKeyBase64 = Buffer.from(privateKey).toString('base64');
|
||||
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'GITHUB_APP_ID') return '123456';
|
||||
if (key === 'GITHUB_APP_PRIVATE_KEY_BASE64') return privateKeyBase64;
|
||||
if (key === 'GITHUB_WEBHOOK_SECRET') return 'webhook-secret';
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
});
|
||||
|
||||
(App as any).mockImplementation(() => {
|
||||
throw new Error('Failed to create app');
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
expect(() => createGitHubApp()).toThrow('Failed to create GitHub App: Failed to create app');
|
||||
await expect(createGitHubApp()).rejects.toThrow('Failed to create GitHub App: Failed to create app');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,93 +7,106 @@ import {
|
|||
verifyGitHubWebhookSignature,
|
||||
} from './verify-webhook-signature';
|
||||
|
||||
describe('verify-webhook-signature', () => {
|
||||
const originalEnv = process.env;
|
||||
// Mock @buster/secrets
|
||||
vi.mock('@buster/secrets', () => ({
|
||||
getSecret: vi.fn(),
|
||||
GITHUB_KEYS: {
|
||||
GITHUB_APP_ID: 'GITHUB_APP_ID',
|
||||
GITHUB_APP_NAME: 'GITHUB_APP_NAME',
|
||||
GITHUB_APP_PRIVATE_KEY_BASE64: 'GITHUB_APP_PRIVATE_KEY_BASE64',
|
||||
GITHUB_APP_PRIVATE_KEY_BASE: 'GITHUB_APP_PRIVATE_KEY_BASE',
|
||||
GITHUB_WEBHOOK_SECRET: 'GITHUB_WEBHOOK_SECRET',
|
||||
GITHUB_TOKEN: 'GITHUB_TOKEN',
|
||||
},
|
||||
}));
|
||||
|
||||
import { getSecret } from '@buster/secrets';
|
||||
|
||||
describe('verify-webhook-signature', () => {
|
||||
beforeEach(() => {
|
||||
// Set up test environment
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
GITHUB_APP_ID: '123456',
|
||||
GITHUB_APP_PRIVATE_KEY_BASE64: Buffer.from(
|
||||
'-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----'
|
||||
).toString('base64'),
|
||||
GITHUB_WEBHOOK_SECRET: 'test-webhook-secret',
|
||||
};
|
||||
vi.clearAllMocks();
|
||||
// Default mock for getSecret
|
||||
vi.mocked(getSecret).mockImplementation(async (key: string) => {
|
||||
if (key === 'GITHUB_APP_ID') return '123456';
|
||||
if (key === 'GITHUB_APP_PRIVATE_KEY_BASE64') return Buffer.from(
|
||||
'-----BEGIN RSA PRIVATE KEY-----\ntest-key\n-----END RSA PRIVATE KEY-----'
|
||||
).toString('base64');
|
||||
if (key === 'GITHUB_WEBHOOK_SECRET') return 'test-webhook-secret';
|
||||
throw new Error(`Unexpected key: ${key}`);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('verifyGitHubWebhookSignature', () => {
|
||||
it('should return true for valid signature', () => {
|
||||
it('should return true for valid signature', async () => {
|
||||
// Arrange
|
||||
const payload = JSON.stringify({ test: 'data' });
|
||||
const signature = `sha256=${createHmac('sha256', 'test-webhook-secret').update(payload).digest('hex')}`;
|
||||
|
||||
// Act
|
||||
const result = verifyGitHubWebhookSignature(payload, signature);
|
||||
const result = await verifyGitHubWebhookSignature(payload, signature);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid signature', () => {
|
||||
it('should return false for invalid signature', async () => {
|
||||
// Arrange
|
||||
const payload = JSON.stringify({ test: 'data' });
|
||||
const signature = 'sha256=invalid-signature';
|
||||
|
||||
// Act
|
||||
const result = verifyGitHubWebhookSignature(payload, signature);
|
||||
const result = await verifyGitHubWebhookSignature(payload, signature);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when signature is missing', () => {
|
||||
it('should return false when signature is missing', async () => {
|
||||
// Arrange
|
||||
const payload = JSON.stringify({ test: 'data' });
|
||||
|
||||
// Act
|
||||
const result = verifyGitHubWebhookSignature(payload, undefined);
|
||||
const result = await verifyGitHubWebhookSignature(payload, undefined);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when signature format is invalid', () => {
|
||||
it('should return false when signature format is invalid', async () => {
|
||||
// Arrange
|
||||
const payload = JSON.stringify({ test: 'data' });
|
||||
const signature = 'invalid-format-signature';
|
||||
|
||||
// Act
|
||||
const result = verifyGitHubWebhookSignature(payload, signature);
|
||||
const result = await verifyGitHubWebhookSignature(payload, signature);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when signature has wrong algorithm', () => {
|
||||
it('should return false when signature has wrong algorithm', async () => {
|
||||
// Arrange
|
||||
const payload = JSON.stringify({ test: 'data' });
|
||||
const signature = `sha1=${createHmac('sha1', 'test-webhook-secret').update(payload).digest('hex')}`;
|
||||
|
||||
// Act
|
||||
const result = verifyGitHubWebhookSignature(payload, signature);
|
||||
const result = await verifyGitHubWebhookSignature(payload, signature);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle different payload types', () => {
|
||||
it('should handle different payload types', async () => {
|
||||
// Arrange
|
||||
const payload = 'plain-text-payload';
|
||||
const signature = `sha256=${createHmac('sha256', 'test-webhook-secret').update(payload).digest('hex')}`;
|
||||
|
||||
// Act
|
||||
const result = verifyGitHubWebhookSignature(payload, signature);
|
||||
const result = await verifyGitHubWebhookSignature(payload, signature);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
|
@ -156,7 +169,7 @@ describe('verify-webhook-signature', () => {
|
|||
});
|
||||
|
||||
describe('verifyGitHubWebhook', () => {
|
||||
it('should not throw for valid webhook', () => {
|
||||
it('should not throw for valid webhook', async () => {
|
||||
// Arrange
|
||||
const payload = JSON.stringify({ test: 'data' });
|
||||
const signature = `sha256=${createHmac('sha256', 'test-webhook-secret').update(payload).digest('hex')}`;
|
||||
|
@ -165,21 +178,21 @@ describe('verify-webhook-signature', () => {
|
|||
};
|
||||
|
||||
// Act & Assert
|
||||
expect(() => verifyGitHubWebhook(payload, headers)).not.toThrow();
|
||||
await expect(verifyGitHubWebhook(payload, headers)).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw when signature is missing', () => {
|
||||
it('should throw when signature is missing', async () => {
|
||||
// Arrange
|
||||
const payload = JSON.stringify({ test: 'data' });
|
||||
const headers = {};
|
||||
|
||||
// Act & Assert
|
||||
expect(() => verifyGitHubWebhook(payload, headers)).toThrow(
|
||||
await expect(verifyGitHubWebhook(payload, headers)).rejects.toThrow(
|
||||
'Missing X-Hub-Signature-256 header'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when signature is invalid', () => {
|
||||
it('should throw when signature is invalid', async () => {
|
||||
// Arrange
|
||||
const payload = JSON.stringify({ test: 'data' });
|
||||
const headers = {
|
||||
|
@ -187,10 +200,10 @@ describe('verify-webhook-signature', () => {
|
|||
};
|
||||
|
||||
// Act & Assert
|
||||
expect(() => verifyGitHubWebhook(payload, headers)).toThrow('Invalid webhook signature');
|
||||
await expect(verifyGitHubWebhook(payload, headers)).rejects.toThrow('Invalid webhook signature');
|
||||
});
|
||||
|
||||
it('should include error code in thrown error', () => {
|
||||
it('should include error code in thrown error', async () => {
|
||||
// Arrange
|
||||
const payload = JSON.stringify({ test: 'data' });
|
||||
const headers = {
|
||||
|
@ -200,7 +213,7 @@ describe('verify-webhook-signature', () => {
|
|||
// Act
|
||||
let error: any;
|
||||
try {
|
||||
verifyGitHubWebhook(payload, headers);
|
||||
await verifyGitHubWebhook(payload, headers);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,52 @@ import { HTTPException } from 'hono/http-exception';
|
|||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { SlackHandler } from './handler';
|
||||
|
||||
// Mock @buster/secrets
|
||||
vi.mock('@buster/secrets', () => ({
|
||||
getSecret: vi.fn().mockImplementation(async (key: string) => {
|
||||
const secrets: Record<string, string> = {
|
||||
BUSTER_URL: 'https://test.com',
|
||||
SLACK_CLIENT_ID: 'test-client-id',
|
||||
SLACK_CLIENT_SECRET: 'test-client-secret',
|
||||
SERVER_URL: 'https://test.com',
|
||||
};
|
||||
return secrets[key] || '';
|
||||
}),
|
||||
SERVER_KEYS: {
|
||||
DATABASE_URL: 'DATABASE_URL',
|
||||
SUPABASE_URL: 'SUPABASE_URL',
|
||||
SUPABASE_SERVICE_ROLE_KEY: 'SUPABASE_SERVICE_ROLE_KEY',
|
||||
ELECTRIC_PROXY_URL: 'ELECTRIC_PROXY_URL',
|
||||
ELECTRIC_SOURCE_ID: 'ELECTRIC_SOURCE_ID',
|
||||
ELECTRIC_SECRET: 'ELECTRIC_SECRET',
|
||||
TRIGGER_SECRET_KEY: 'TRIGGER_SECRET_KEY',
|
||||
SLACK_CLIENT_ID: 'SLACK_CLIENT_ID',
|
||||
SLACK_CLIENT_SECRET: 'SLACK_CLIENT_SECRET',
|
||||
SLACK_SIGNING_SECRET: 'SLACK_SIGNING_SECRET',
|
||||
SLACK_APP_SUPPORT_URL: 'SLACK_APP_SUPPORT_URL',
|
||||
SERVER_PORT: 'SERVER_PORT',
|
||||
SERVER_URL: 'SERVER_URL',
|
||||
BUSTER_URL: 'BUSTER_URL',
|
||||
ENVIRONMENT: 'ENVIRONMENT',
|
||||
},
|
||||
SLACK_KEYS: {
|
||||
SLACK_CLIENT_ID: 'SLACK_CLIENT_ID',
|
||||
SLACK_CLIENT_SECRET: 'SLACK_CLIENT_SECRET',
|
||||
SLACK_SIGNING_SECRET: 'SLACK_SIGNING_SECRET',
|
||||
SLACK_REDIRECT_URI: 'SLACK_REDIRECT_URI',
|
||||
SLACK_BOT_TOKEN: 'SLACK_BOT_TOKEN',
|
||||
SLACK_CHANNEL_ID: 'SLACK_CHANNEL_ID',
|
||||
SLACK_TEST_JOIN_CHANNEL_ID: 'SLACK_TEST_JOIN_CHANNEL_ID',
|
||||
SLACK_TEST_LEAVE_CHANNEL_ID: 'SLACK_TEST_LEAVE_CHANNEL_ID',
|
||||
SLACK_TEST_ACCESS_TOKEN: 'SLACK_TEST_ACCESS_TOKEN',
|
||||
SLACK_SKIP_DELETE_TESTS: 'SLACK_SKIP_DELETE_TESTS',
|
||||
SLACK_SKIP_LEAVE_TESTS: 'SLACK_SKIP_LEAVE_TESTS',
|
||||
SLACK_APP_SUPPORT_URL: 'SLACK_APP_SUPPORT_URL',
|
||||
BUSTER_ALERT_CHANNEL_TOKEN: 'BUSTER_ALERT_CHANNEL_TOKEN',
|
||||
BUSTER_ALERT_CHANNEL_ID: 'BUSTER_ALERT_CHANNEL_ID',
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock dependencies
|
||||
const mockSlackOAuthService = {
|
||||
isEnabled: vi.fn(),
|
||||
|
@ -136,7 +182,7 @@ describe('SlackHandler', () => {
|
|||
await handler.handleOAuthCallback(mockContext);
|
||||
|
||||
expect(mockContext.redirect).toHaveBeenCalledWith(
|
||||
'/app/settings/integrations?status=cancelled'
|
||||
'https://test.com/app/settings/integrations?status=cancelled'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -146,7 +192,7 @@ describe('SlackHandler', () => {
|
|||
await handler.handleOAuthCallback(mockContext);
|
||||
|
||||
expect(mockContext.redirect).toHaveBeenCalledWith(
|
||||
'/app/settings/integrations?status=error&error=invalid_parameters'
|
||||
'https://test.com/app/settings/integrations?status=error&error=invalid_parameters'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -171,7 +217,7 @@ describe('SlackHandler', () => {
|
|||
state: 'test-state',
|
||||
});
|
||||
expect(mockContext.redirect).toHaveBeenCalledWith(
|
||||
'/dashboard?status=success&workspace=Test%20Workspace'
|
||||
'https://test.com/dashboard?status=success&workspace=Test%20Workspace'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -191,7 +237,7 @@ describe('SlackHandler', () => {
|
|||
await handler.handleOAuthCallback(mockContext);
|
||||
|
||||
expect(mockContext.redirect).toHaveBeenCalledWith(
|
||||
'/app/settings/integrations?status=error&error=invalid_state'
|
||||
'https://test.com/app/settings/integrations?status=error&error=invalid_state'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,19 @@ import { HTTPException } from 'hono/http-exception';
|
|||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { githubWebhookValidator } from './github-webhook-validator';
|
||||
|
||||
// Mock @buster/secrets
|
||||
vi.mock('@buster/secrets', () => ({
|
||||
getSecret: vi.fn(),
|
||||
GITHUB_KEYS: {
|
||||
GITHUB_APP_ID: 'GITHUB_APP_ID',
|
||||
GITHUB_APP_NAME: 'GITHUB_APP_NAME',
|
||||
GITHUB_APP_PRIVATE_KEY_BASE64: 'GITHUB_APP_PRIVATE_KEY_BASE64',
|
||||
GITHUB_APP_PRIVATE_KEY_BASE: 'GITHUB_APP_PRIVATE_KEY_BASE',
|
||||
GITHUB_WEBHOOK_SECRET: 'GITHUB_WEBHOOK_SECRET',
|
||||
GITHUB_TOKEN: 'GITHUB_TOKEN',
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock the verify webhook signature from github package
|
||||
vi.mock('@buster/github', () => ({
|
||||
verifyGitHubWebhookSignature: vi.fn(),
|
||||
|
@ -12,10 +25,13 @@ vi.mock('@buster/github', () => ({
|
|||
}));
|
||||
|
||||
import { InstallationCallbackSchema, verifyGitHubWebhookSignature } from '@buster/github';
|
||||
import { getSecret } from '@buster/secrets';
|
||||
|
||||
describe('githubWebhookValidator', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Default mock for getSecret - can be overridden in individual tests
|
||||
vi.mocked(getSecret).mockResolvedValue('test-webhook-secret');
|
||||
});
|
||||
const mockPayload = {
|
||||
action: 'created' as const,
|
||||
|
@ -51,8 +67,6 @@ describe('githubWebhookValidator', () => {
|
|||
it('should validate a valid webhook request', async () => {
|
||||
const { app, headers, body } = createMockContext('sha256=test-signature');
|
||||
|
||||
// Mock environment variable
|
||||
process.env.GITHUB_WEBHOOK_SECRET = 'test-secret';
|
||||
|
||||
// Mock signature verification to return true
|
||||
vi.mocked(verifyGitHubWebhookSignature).mockReturnValue(true);
|
||||
|
@ -109,8 +123,8 @@ describe('githubWebhookValidator', () => {
|
|||
it('should reject request when webhook secret is not configured', async () => {
|
||||
const { app, headers, body } = createMockContext('sha256=test-signature');
|
||||
|
||||
// Remove environment variable
|
||||
delete process.env.GITHUB_WEBHOOK_SECRET;
|
||||
// Mock getSecret to reject for webhook secret
|
||||
vi.mocked(getSecret).mockRejectedValue(new Error('Secret not found'));
|
||||
|
||||
const res = await app.request('/', {
|
||||
method: 'POST',
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
// Mock @buster/secrets
|
||||
vi.mock('@buster/secrets', () => ({
|
||||
getSecret: vi.fn().mockImplementation(async (key: string) => {
|
||||
// Return process.env values to allow tests to control them
|
||||
return process.env[key];
|
||||
}),
|
||||
SHARED_KEYS: {
|
||||
NODE_ENV: 'NODE_ENV',
|
||||
ENVIRONMENT: 'ENVIRONMENT',
|
||||
DATABASE_URL: 'DATABASE_URL',
|
||||
SUPABASE_URL: 'SUPABASE_URL',
|
||||
SUPABASE_SERVICE_ROLE_KEY: 'SUPABASE_SERVICE_ROLE_KEY',
|
||||
SUPABASE_ANON_KEY: 'SUPABASE_ANON_KEY',
|
||||
LOG_LEVEL: 'LOG_LEVEL',
|
||||
CI: 'CI',
|
||||
},
|
||||
}));
|
||||
|
||||
describe('logger middleware', () => {
|
||||
let originalEnv: NodeJS.ProcessEnv;
|
||||
let originalConsole: {
|
||||
|
|
|
@ -5,7 +5,6 @@ import { AnalystAgentTaskInputSchema, type AnalystAgentTaskOutput } from './type
|
|||
|
||||
// Task 2 & 4: Database helpers (IMPLEMENTED)
|
||||
import {
|
||||
dbInitialized,
|
||||
getBraintrustMetadata,
|
||||
getChatConversationHistory,
|
||||
getMessageContext,
|
||||
|
@ -240,9 +239,6 @@ export const analystAgentTask: ReturnType<
|
|||
const taskStartTime = Date.now();
|
||||
const resourceTracker = new ResourceTracker();
|
||||
|
||||
// Ensure database is initialized before any queries
|
||||
await dbInitialized;
|
||||
|
||||
// Log initial performance metrics
|
||||
logPerformanceMetrics('task-start', payload.message_id, taskStartTime, resourceTracker);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { randomBytes } from 'node:crypto';
|
|||
import { type AssetPermissionCheck, checkPermission } from '@buster/access-controls';
|
||||
import { createAdapter, getProviderForOrganization } from '@buster/data-source';
|
||||
import type { Credentials } from '@buster/data-source';
|
||||
import { dbInitialized, getDataSourceCredentials, getMetricForExport } from '@buster/database';
|
||||
import { getDataSourceCredentials, getMetricForExport } from '@buster/database';
|
||||
import { logger, schemaTask } from '@trigger.dev/sdk';
|
||||
import { convertToCSV, estimateCSVSize } from './csv-helpers';
|
||||
import {
|
||||
|
@ -48,9 +48,6 @@ export const exportMetricData: ReturnType<
|
|||
run: async (payload): Promise<ExportMetricDataOutput> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Ensure database is initialized before any queries
|
||||
await dbInitialized;
|
||||
|
||||
try {
|
||||
logger.log('Starting metric export', {
|
||||
metricId: payload.metricId,
|
||||
|
|
|
@ -10,7 +10,15 @@ import {
|
|||
|
||||
// Mock the database module
|
||||
vi.mock('@buster/database', () => ({
|
||||
getDb: vi.fn(),
|
||||
db: {
|
||||
select: vi.fn(),
|
||||
from: vi.fn(),
|
||||
innerJoin: vi.fn(),
|
||||
leftJoin: vi.fn(),
|
||||
where: vi.fn(),
|
||||
orderBy: vi.fn(),
|
||||
limit: vi.fn(),
|
||||
},
|
||||
and: vi.fn((...args) => ({ type: 'and', args })),
|
||||
eq: vi.fn((a, b) => ({ type: 'eq', a, b })),
|
||||
lt: vi.fn((a, b) => ({ type: 'lt', a, b })),
|
||||
|
@ -27,7 +35,8 @@ vi.mock('@buster/database', () => ({
|
|||
rawLlmMessages: 'messages.rawLlmMessages',
|
||||
},
|
||||
chats: { id: 'chats.id', organizationId: 'chats.organizationId' },
|
||||
users: { id: 'users.id', name: 'users.name' },
|
||||
users: { id: 'users.id', name: 'users.name', email: 'users.email' },
|
||||
getChatConversationHistory: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock access controls
|
||||
|
@ -40,24 +49,20 @@ describe('message-fetchers', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockDb = {
|
||||
select: vi.fn().mockReturnThis(),
|
||||
from: vi.fn().mockReturnThis(),
|
||||
innerJoin: vi.fn().mockReturnThis(),
|
||||
leftJoin: vi.fn().mockReturnThis(),
|
||||
where: vi.fn().mockReturnThis(),
|
||||
orderBy: vi.fn(),
|
||||
limit: vi.fn(),
|
||||
};
|
||||
|
||||
|
||||
// Get the mocked db object from the module mock
|
||||
mockDb = vi.mocked(database.db);
|
||||
|
||||
// Set up the mock chain to return itself for most methods
|
||||
mockDb.select.mockReturnValue(mockDb);
|
||||
mockDb.from.mockReturnValue(mockDb);
|
||||
mockDb.innerJoin.mockReturnValue(mockDb);
|
||||
mockDb.leftJoin.mockReturnValue(mockDb);
|
||||
mockDb.where.mockReturnValue(mockDb);
|
||||
|
||||
vi.mocked(database.getDb).mockReturnValue(mockDb);
|
||||
mockDb.orderBy.mockReturnValue(mockDb);
|
||||
|
||||
// Also mock getChatConversationHistory
|
||||
vi.mocked(database.getChatConversationHistory).mockResolvedValue([]);
|
||||
});
|
||||
|
||||
describe('fetchMessageWithContext', () => {
|
||||
|
@ -69,10 +74,16 @@ describe('message-fetchers', () => {
|
|||
createdAt: '2024-01-01T00:00:00Z',
|
||||
rawLlmMessages: [{ role: 'user', content: 'Test message' }],
|
||||
userName: 'John Doe',
|
||||
userEmail: 'john@example.com',
|
||||
organizationId: '423e4567-e89b-12d3-a456-426614174000',
|
||||
};
|
||||
|
||||
mockDb.limit.mockResolvedValue([messageData]);
|
||||
|
||||
// Mock getChatConversationHistory to return the expected messages
|
||||
vi.mocked(database.getChatConversationHistory).mockResolvedValue(
|
||||
messageData.rawLlmMessages as any
|
||||
);
|
||||
|
||||
const result = await fetchMessageWithContext(messageData.id);
|
||||
|
||||
|
@ -107,10 +118,16 @@ describe('message-fetchers', () => {
|
|||
createdAt: '2024-01-01T00:00:00Z',
|
||||
rawLlmMessages: [{ role: 'user', content: 'Test' }],
|
||||
userName: null,
|
||||
userEmail: null,
|
||||
organizationId: '423e4567-e89b-12d3-a456-426614174000',
|
||||
};
|
||||
|
||||
mockDb.limit.mockResolvedValue([messageData]);
|
||||
|
||||
// Mock getChatConversationHistory to return the expected messages
|
||||
vi.mocked(database.getChatConversationHistory).mockResolvedValue(
|
||||
messageData.rawLlmMessages as any
|
||||
);
|
||||
|
||||
const result = await fetchMessageWithContext(messageData.id);
|
||||
expect(result.userName).toBe('Unknown');
|
||||
|
|
|
@ -2,7 +2,7 @@ import { getPermissionedDatasets } from '@buster/access-controls';
|
|||
import {
|
||||
and,
|
||||
chats,
|
||||
dbInitialized,
|
||||
db,
|
||||
eq,
|
||||
getChatConversationHistory,
|
||||
isNotNull,
|
||||
|
@ -23,8 +23,6 @@ import {
|
|||
* Fetch current message with user and chat info
|
||||
*/
|
||||
export async function fetchMessageWithContext(messageId: string): Promise<MessageContext> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
try {
|
||||
const result = await db
|
||||
.select({
|
||||
|
@ -88,8 +86,6 @@ export async function fetchPreviousPostProcessingMessages(
|
|||
chatId: string,
|
||||
beforeTimestamp: Date
|
||||
): Promise<PostProcessingResult[]> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
try {
|
||||
const result = await db
|
||||
.select({
|
||||
|
|
|
@ -12,20 +12,20 @@ vi.mock('@buster/slack', () => ({
|
|||
|
||||
// Mock the database
|
||||
vi.mock('@buster/database', () => ({
|
||||
getDb: vi.fn(() => ({
|
||||
db: {
|
||||
select: vi.fn(() => ({
|
||||
from: vi.fn(() => ({
|
||||
where: vi.fn(() => ({
|
||||
limit: vi.fn(() =>
|
||||
Promise.resolve([
|
||||
{ id: 'integration-1', defaultChannel: { id: 'C123' }, tokenVaultKey: 'vault-key-1' },
|
||||
{ id: 'integration-1', defaultChannel: { id: 'C123' }, tokenVaultKey: 'vault-key-1', status: 'active' },
|
||||
])
|
||||
),
|
||||
})),
|
||||
})),
|
||||
})),
|
||||
transaction: vi.fn(),
|
||||
})),
|
||||
},
|
||||
getSecretByName: vi.fn(() => Promise.resolve({ secret: 'xoxb-test-token' })),
|
||||
eq: vi.fn(),
|
||||
and: vi.fn(),
|
||||
|
@ -36,6 +36,24 @@ vi.mock('@buster/database', () => ({
|
|||
slackMessageTracking: {},
|
||||
}));
|
||||
|
||||
// Mock the @buster/secrets module
|
||||
vi.mock('@buster/secrets', () => ({
|
||||
SERVER_KEYS: {
|
||||
BUSTER_URL: 'BUSTER_URL',
|
||||
},
|
||||
SLACK_KEYS: {
|
||||
BUSTER_ALERT_CHANNEL_TOKEN: 'BUSTER_ALERT_CHANNEL_TOKEN',
|
||||
BUSTER_ALERT_CHANNEL_ID: 'BUSTER_ALERT_CHANNEL_ID',
|
||||
},
|
||||
getSecret: vi.fn((key) => {
|
||||
if (key === 'BUSTER_URL') {
|
||||
return Promise.resolve('https://platform.buster.so');
|
||||
}
|
||||
// Return null for alert channel keys to skip that step in tests
|
||||
return Promise.resolve(null);
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = vi.fn();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
and,
|
||||
dbInitialized,
|
||||
db,
|
||||
eq,
|
||||
getSecretByName,
|
||||
isNull,
|
||||
|
@ -77,8 +77,6 @@ export async function getExistingSlackMessageForChat(chatId: string): Promise<{
|
|||
integrationId?: string;
|
||||
} | null> {
|
||||
try {
|
||||
const db = await dbInitialized;
|
||||
|
||||
// Find messages from the same chat that have been sent to Slack
|
||||
const existingSlackMessages = await db
|
||||
.select({
|
||||
|
@ -151,7 +149,6 @@ export async function sendSlackNotification(
|
|||
): Promise<SlackNotificationResult> {
|
||||
try {
|
||||
// Step 1: Check if organization has active Slack integration
|
||||
const db = await dbInitialized;
|
||||
const [integration] = await db
|
||||
.select()
|
||||
.from(slackIntegrations)
|
||||
|
@ -610,8 +607,6 @@ export async function trackSlackNotification(params: {
|
|||
summaryMessage?: string;
|
||||
slackBlocks?: SlackBlock[];
|
||||
}): Promise<void> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
try {
|
||||
await db.transaction(async (tx) => {
|
||||
// Insert into slack_message_tracking
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
import {
|
||||
chats,
|
||||
db as dbImport,
|
||||
dbInitialized,
|
||||
eq,
|
||||
messages,
|
||||
organizations,
|
||||
users,
|
||||
} from '@buster/database';
|
||||
import { chats, db as dbImport, eq, messages, organizations, users } from '@buster/database';
|
||||
import { runs, tasks } from '@trigger.dev/sdk';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
@ -230,7 +222,7 @@ describe.skipIf(skipIntegrationTests)('messagePostProcessingTask integration', (
|
|||
expect(result.output?.result?.workflowCompleted).toBe(true);
|
||||
|
||||
// Verify database was updated
|
||||
const db = await dbInitialized;
|
||||
const db = dbImport;
|
||||
const updatedMessage = await db
|
||||
.select({ postProcessingMessage: messages.postProcessingMessage })
|
||||
.from(messages)
|
||||
|
@ -257,7 +249,7 @@ describe.skipIf(skipIntegrationTests)('messagePostProcessingTask integration', (
|
|||
});
|
||||
|
||||
// Manually add post-processing result to first message
|
||||
const db = await dbInitialized;
|
||||
const db = dbImport;
|
||||
await db
|
||||
.update(messages)
|
||||
.set({
|
||||
|
@ -308,7 +300,7 @@ describe.skipIf(skipIntegrationTests)('messagePostProcessingTask integration', (
|
|||
expect(result.output?.messageId).toBe(messageId);
|
||||
|
||||
// Cleanup - reset postProcessingMessage to null
|
||||
const db = await dbInitialized;
|
||||
const db = dbImport;
|
||||
await db
|
||||
.update(messages)
|
||||
.set({ postProcessingMessage: null })
|
||||
|
@ -339,7 +331,7 @@ describe.skipIf(skipIntegrationTests)('messagePostProcessingTask integration', (
|
|||
expect(duration).toBeLessThan(60000);
|
||||
|
||||
// Cleanup - reset postProcessingMessage to null
|
||||
const db = await dbInitialized;
|
||||
const db = dbImport;
|
||||
await db
|
||||
.update(messages)
|
||||
.set({ postProcessingMessage: null })
|
||||
|
|
|
@ -23,6 +23,11 @@ vi.mock('./helpers', () => ({
|
|||
}));
|
||||
|
||||
vi.mock('@buster/database', () => ({
|
||||
db: {
|
||||
update: vi.fn(),
|
||||
set: vi.fn(),
|
||||
where: vi.fn(),
|
||||
},
|
||||
getDb: vi.fn(),
|
||||
eq: vi.fn((a, b) => ({ type: 'eq', a, b })),
|
||||
messages: { id: 'messages.id', postProcessingMessage: 'messages.postProcessingMessage' },
|
||||
|
@ -79,7 +84,17 @@ describe('messagePostProcessingTask', () => {
|
|||
vi.clearAllMocks();
|
||||
// Mock BRAINTRUST_KEY for unit tests
|
||||
vi.stubEnv('BRAINTRUST_KEY', 'test-braintrust-key');
|
||||
mockDb = {
|
||||
|
||||
// Get the mocked db object from the module mock
|
||||
mockDb = vi.mocked(database.db);
|
||||
|
||||
// Set up the mock chain to return itself for most methods
|
||||
mockDb.update.mockReturnValue(mockDb);
|
||||
mockDb.set.mockReturnValue(mockDb);
|
||||
mockDb.where.mockReturnValue(mockDb);
|
||||
|
||||
// Also keep the old getDb mock for backward compatibility if still used
|
||||
const mockGetDb = {
|
||||
update: vi.fn().mockReturnThis(),
|
||||
set: vi.fn().mockReturnThis(),
|
||||
where: vi.fn().mockReturnThis(),
|
||||
|
@ -88,13 +103,10 @@ describe('messagePostProcessingTask', () => {
|
|||
limit: vi.fn().mockReturnThis(),
|
||||
orderBy: vi.fn().mockReturnThis(),
|
||||
};
|
||||
|
||||
// Default mock chain behavior
|
||||
mockDb.where.mockReturnValue(mockDb);
|
||||
mockDb.limit.mockResolvedValue([{ tokenVaultKey: 'vault-key-123' }]);
|
||||
mockDb.orderBy.mockResolvedValue([]);
|
||||
|
||||
vi.mocked(database.getDb).mockReturnValue(mockDb);
|
||||
mockGetDb.where.mockReturnValue(mockGetDb);
|
||||
mockGetDb.limit.mockResolvedValue([{ tokenVaultKey: 'vault-key-123' }]);
|
||||
mockGetDb.orderBy.mockResolvedValue([]);
|
||||
vi.mocked(database.getDb).mockReturnValue(mockGetDb);
|
||||
});
|
||||
|
||||
it('should process message successfully for initial message', async () => {
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
import postProcessingWorkflow, {
|
||||
type PostProcessingWorkflowOutput,
|
||||
} from '@buster/ai/workflows/message-post-processing-workflow/message-post-processing-workflow';
|
||||
import {
|
||||
dbInitialized,
|
||||
eq,
|
||||
getBraintrustMetadata,
|
||||
messages,
|
||||
slackIntegrations,
|
||||
} from '@buster/database';
|
||||
import { db, eq, getBraintrustMetadata, messages, slackIntegrations } from '@buster/database';
|
||||
import { BRAINTRUST_KEYS, SERVER_KEYS, getSecret } from '@buster/secrets';
|
||||
import type {
|
||||
AssumptionClassification,
|
||||
|
@ -214,7 +208,6 @@ export const messagePostProcessingTask: ReturnType<
|
|||
const dbData = extractDbFields(validatedOutput, messageContext.userName);
|
||||
|
||||
try {
|
||||
const db = await dbInitialized;
|
||||
await db
|
||||
.update(messages)
|
||||
.set({
|
||||
|
@ -285,7 +278,6 @@ export const messagePostProcessingTask: ReturnType<
|
|||
});
|
||||
|
||||
// Need to get integration details to get the token vault key
|
||||
const db = await dbInitialized;
|
||||
const [integration] = await db
|
||||
.select({ tokenVaultKey: slackIntegrations.tokenVaultKey })
|
||||
.from(slackIntegrations)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { db } from '@buster/database';
|
||||
import { esbuildPlugin } from '@trigger.dev/build/extensions';
|
||||
import { defineConfig } from '@trigger.dev/sdk';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
|
|
@ -42,8 +42,7 @@ vi.mock('@buster/database', () => {
|
|||
const mockDbInstance = createChainableMock();
|
||||
|
||||
return {
|
||||
getDb: vi.fn(() => Promise.resolve(mockDbInstance)),
|
||||
dbInitialized: Promise.resolve(mockDbInstance),
|
||||
db: mockDbInstance,
|
||||
and: vi.fn((...args) => ({ type: 'and', args })),
|
||||
eq: vi.fn((a, b) => ({ type: 'eq', a, b })),
|
||||
isNull: vi.fn((field) => ({ type: 'isNull', field })),
|
||||
|
@ -88,9 +87,9 @@ describe('Access Controls Unit Tests - Organization Default Permission Group', (
|
|||
});
|
||||
|
||||
it('should return empty array when user has no organization', async () => {
|
||||
// Get the dbInitialized mock
|
||||
// Get the db mock
|
||||
const dbModule = await import('@buster/database');
|
||||
const mockDb = await dbModule.dbInitialized;
|
||||
const mockDb = dbModule.db;
|
||||
|
||||
// Mock for user organization query (returns empty)
|
||||
mockDb.select.mockReturnValueOnce(mockDb);
|
||||
|
@ -105,9 +104,9 @@ describe('Access Controls Unit Tests - Organization Default Permission Group', (
|
|||
});
|
||||
|
||||
it('should return datasets for admin users', async () => {
|
||||
// Get the dbInitialized mock
|
||||
// Get the db mock
|
||||
const dbModule = await import('@buster/database');
|
||||
const mockDb = await dbModule.dbInitialized;
|
||||
const mockDb = dbModule.db;
|
||||
|
||||
// Mock for user organization query - returns admin user
|
||||
mockDb.select.mockReturnValueOnce(mockDb);
|
||||
|
@ -155,9 +154,9 @@ describe('Access Controls Unit Tests - Organization Default Permission Group', (
|
|||
});
|
||||
|
||||
it('should return false when dataset is deleted', async () => {
|
||||
// Get the dbInitialized mock
|
||||
// Get the db mock
|
||||
const dbModule = await import('@buster/database');
|
||||
const mockDb = await dbModule.dbInitialized;
|
||||
const mockDb = dbModule.db;
|
||||
|
||||
// Clear previous mock calls
|
||||
vi.clearAllMocks();
|
||||
|
@ -198,9 +197,9 @@ describe('Access Controls Unit Tests - Organization Default Permission Group', (
|
|||
});
|
||||
|
||||
it('should return false when not all datasets exist', async () => {
|
||||
// Get the dbInitialized mock
|
||||
// Get the db mock
|
||||
const dbModule = await import('@buster/database');
|
||||
const mockDb = await dbModule.dbInitialized;
|
||||
const mockDb = dbModule.db;
|
||||
|
||||
// Clear previous mock calls
|
||||
vi.clearAllMocks();
|
||||
|
@ -226,9 +225,9 @@ describe('Access Controls Unit Tests - Organization Default Permission Group', (
|
|||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle database connection errors gracefully', async () => {
|
||||
// Get the dbInitialized mock
|
||||
// Get the db mock
|
||||
const dbModule = await import('@buster/database');
|
||||
const mockDb = await dbModule.dbInitialized;
|
||||
const mockDb = dbModule.db;
|
||||
|
||||
// Clear previous mock calls
|
||||
vi.clearAllMocks();
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
datasetPermissions,
|
||||
datasets,
|
||||
datasetsToPermissionGroups,
|
||||
dbInitialized,
|
||||
db,
|
||||
eq,
|
||||
inArray,
|
||||
isNull,
|
||||
|
@ -60,8 +60,6 @@ const HasAllDatasetsAccessSchema = z.object({
|
|||
|
||||
// Path 1: Direct User -> Dataset
|
||||
async function fetchDirectUserDatasetIds(userId: string): Promise<string[]> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
const results = await db
|
||||
.select({ datasetId: datasetPermissions.datasetId })
|
||||
.from(datasetPermissions)
|
||||
|
@ -78,8 +76,6 @@ async function fetchDirectUserDatasetIds(userId: string): Promise<string[]> {
|
|||
|
||||
// Path 3: User -> Team -> Dataset (Direct team assignment)
|
||||
async function fetchTeamDirectDatasetIds(userId: string): Promise<string[]> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
const results = await db
|
||||
.selectDistinct({ datasetId: datasetPermissions.datasetId })
|
||||
.from(datasetPermissions)
|
||||
|
@ -99,8 +95,6 @@ async function fetchTeamDirectDatasetIds(userId: string): Promise<string[]> {
|
|||
|
||||
// Path 2: User -> Group -> Dataset
|
||||
async function fetchUserGroupDatasetIds(userId: string): Promise<string[]> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
const results = await db
|
||||
.selectDistinct({ datasetId: datasetsToPermissionGroups.datasetId })
|
||||
.from(datasetsToPermissionGroups)
|
||||
|
@ -127,8 +121,6 @@ async function fetchUserGroupDatasetIds(userId: string): Promise<string[]> {
|
|||
|
||||
// Path 4: User -> Team -> Group -> Dataset
|
||||
async function fetchTeamGroupDatasetIds(userId: string): Promise<string[]> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
const results = await db
|
||||
.selectDistinct({ datasetId: datasetsToPermissionGroups.datasetId })
|
||||
.from(datasetsToPermissionGroups)
|
||||
|
@ -162,8 +154,6 @@ async function fetchTeamGroupDatasetIds(userId: string): Promise<string[]> {
|
|||
|
||||
// Path 5: User -> Organization -> Default Permission Group -> Dataset
|
||||
async function fetchOrgDefaultDatasetIds(userId: string): Promise<string[]> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
// Get all the user's organizations
|
||||
const userOrgs = await db
|
||||
.select({ organizationId: usersToOrganizations.organizationId })
|
||||
|
@ -229,8 +219,6 @@ export async function getPermissionedDatasets(
|
|||
// Validate inputs
|
||||
const input = GetPermissionedDatasetsSchema.parse({ userId, page, pageSize });
|
||||
|
||||
const db = await dbInitialized;
|
||||
|
||||
// Fetch all user's organizations and roles
|
||||
const userOrgs = await db
|
||||
.select({
|
||||
|
@ -334,8 +322,6 @@ export async function hasDatasetAccess(userId: string, datasetId: string): Promi
|
|||
// Validate inputs
|
||||
const input = HasDatasetAccessSchema.parse({ userId, datasetId });
|
||||
|
||||
const db = await dbInitialized;
|
||||
|
||||
// --- Check if Dataset exists and get Organization ID and deleted status ---
|
||||
const datasetInfo = await db
|
||||
.select({
|
||||
|
@ -408,8 +394,6 @@ export async function hasAllDatasetsAccess(userId: string, datasetIds: string[])
|
|||
return false; // No datasets means no access granted
|
||||
}
|
||||
|
||||
const db = await dbInitialized;
|
||||
|
||||
// --- Step 1: Verify all datasets exist, are not deleted, and get their org IDs ---
|
||||
const datasetInfos = await db
|
||||
.select({
|
||||
|
@ -498,8 +482,6 @@ export async function hasAllDatasetsAccess(userId: string, datasetIds: string[])
|
|||
// --- Helper Functions for Individual Permission Checks ---
|
||||
|
||||
async function checkDirectUserPermission(userId: string, datasetId: string): Promise<boolean> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
const result = await db
|
||||
.select({ count: count() })
|
||||
.from(datasetPermissions)
|
||||
|
@ -518,8 +500,6 @@ async function checkDirectUserPermission(userId: string, datasetId: string): Pro
|
|||
}
|
||||
|
||||
async function checkTeamDirectPermission(userId: string, datasetId: string): Promise<boolean> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
const result = await db
|
||||
.select({ count: count() })
|
||||
.from(datasetPermissions)
|
||||
|
@ -540,8 +520,6 @@ async function checkTeamDirectPermission(userId: string, datasetId: string): Pro
|
|||
}
|
||||
|
||||
async function checkUserGroupPermission(userId: string, datasetId: string): Promise<boolean> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
const result = await db
|
||||
.select({ count: count() })
|
||||
.from(datasetsToPermissionGroups)
|
||||
|
@ -574,8 +552,6 @@ async function checkUserGroupPermission(userId: string, datasetId: string): Prom
|
|||
}
|
||||
|
||||
async function checkTeamGroupPermission(userId: string, datasetId: string): Promise<boolean> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
const result = await db
|
||||
.select({ count: count() })
|
||||
.from(datasetsToPermissionGroups)
|
||||
|
@ -615,8 +591,6 @@ async function checkTeamGroupPermission(userId: string, datasetId: string): Prom
|
|||
}
|
||||
|
||||
async function checkOrgDefaultPermission(userId: string, datasetId: string): Promise<boolean> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
// Get all the user's organizations
|
||||
const userOrgs = await db
|
||||
.select({ organizationId: usersToOrganizations.organizationId })
|
||||
|
|
|
@ -14,8 +14,7 @@ vi.mock('@buster/database', () => {
|
|||
};
|
||||
|
||||
return {
|
||||
getDb: vi.fn(() => Promise.resolve(mockDb)),
|
||||
dbInitialized: Promise.resolve(mockDb),
|
||||
db: mockDb,
|
||||
and: vi.fn((...args) => ({ _and: args })),
|
||||
eq: vi.fn((a, b) => ({ _eq: [a, b] })),
|
||||
isNull: vi.fn((a) => ({ _isNull: a })),
|
||||
|
@ -58,7 +57,7 @@ describe('canUserAccessChat', () => {
|
|||
vi.clearAllMocks();
|
||||
// Get the mock database object
|
||||
const dbModule = await import('@buster/database');
|
||||
mockDb = await dbModule.dbInitialized;
|
||||
mockDb = dbModule.db;
|
||||
|
||||
// Reset all mocks before each test
|
||||
mockDb.select.mockClear();
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
assetTypeEnum,
|
||||
chats,
|
||||
collectionsToAssets,
|
||||
dbInitialized,
|
||||
db as database,
|
||||
eq,
|
||||
identityTypeEnum,
|
||||
isNull,
|
||||
|
@ -28,21 +28,19 @@ export const canUserAccessChat = async ({
|
|||
// Validate inputs
|
||||
const input = CanUserAccessChatSchema.parse({ userId, chatId });
|
||||
|
||||
const db = await dbInitialized;
|
||||
|
||||
// Run all permission checks concurrently for optimal performance
|
||||
const [directPermission, collectionPermission, chatInfo, userOrgs] = await Promise.all([
|
||||
// Check 1: Direct user permission on chat
|
||||
checkDirectChatPermission(db, input.userId, input.chatId),
|
||||
checkDirectChatPermission(database, input.userId, input.chatId),
|
||||
|
||||
// Check 2: User permission through collections
|
||||
checkCollectionChatPermission(db, input.userId, input.chatId),
|
||||
checkCollectionChatPermission(database, input.userId, input.chatId),
|
||||
|
||||
// Check 3: Get chat info (creator & organization)
|
||||
getChatInfo(db, input.chatId),
|
||||
getChatInfo(database, input.chatId),
|
||||
|
||||
// Check 4: Get user's organizations and roles
|
||||
getUserOrganizations(db, input.userId),
|
||||
getUserOrganizations(database, input.userId),
|
||||
]);
|
||||
|
||||
// If chat doesn't exist or is deleted, deny access
|
||||
|
@ -77,7 +75,7 @@ export const canUserAccessChat = async ({
|
|||
|
||||
// Helper function to check direct chat permission
|
||||
async function checkDirectChatPermission(
|
||||
db: Awaited<typeof dbInitialized>,
|
||||
db: typeof database,
|
||||
userId: string,
|
||||
chatId: string
|
||||
): Promise<boolean> {
|
||||
|
@ -100,7 +98,7 @@ async function checkDirectChatPermission(
|
|||
|
||||
// Helper function to check collection-based chat permission
|
||||
async function checkCollectionChatPermission(
|
||||
db: Awaited<typeof dbInitialized>,
|
||||
db: typeof database,
|
||||
userId: string,
|
||||
chatId: string
|
||||
): Promise<boolean> {
|
||||
|
@ -131,7 +129,7 @@ async function checkCollectionChatPermission(
|
|||
|
||||
// Helper function to get chat info (creator and organization)
|
||||
async function getChatInfo(
|
||||
db: Awaited<typeof dbInitialized>,
|
||||
db: typeof database,
|
||||
chatId: string
|
||||
): Promise<{ createdBy: string; organizationId: string } | null> {
|
||||
const result = await db
|
||||
|
@ -148,7 +146,7 @@ async function getChatInfo(
|
|||
|
||||
// Helper function to get user's organizations and roles
|
||||
async function getUserOrganizations(
|
||||
db: Awaited<typeof dbInitialized>,
|
||||
db: typeof database,
|
||||
userId: string
|
||||
): Promise<Array<{ organizationId: string; role: string }>> {
|
||||
const result = await db
|
||||
|
|
|
@ -24,7 +24,7 @@ vi.mock('@buster/database', () => {
|
|||
|
||||
return {
|
||||
getDb: vi.fn(() => Promise.resolve(mockDb)),
|
||||
dbInitialized: Promise.resolve(mockDb),
|
||||
db: mockDb,
|
||||
and: vi.fn((...args) => ({ _and: args })),
|
||||
eq: vi.fn((a, b) => ({ _eq: [a, b] })),
|
||||
isNull: vi.fn((a) => ({ _isNull: a })),
|
||||
|
@ -61,7 +61,7 @@ describe('user-organizations', () => {
|
|||
vi.clearAllMocks();
|
||||
// Get the mock database object
|
||||
const dbModule = await import('@buster/database');
|
||||
mockDb = await dbModule.dbInitialized;
|
||||
mockDb = dbModule.db;
|
||||
|
||||
// Reset mock methods to return 'this' for chaining
|
||||
mockDb.select.mockReturnThis();
|
||||
|
@ -85,8 +85,6 @@ describe('user-organizations', () => {
|
|||
},
|
||||
]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await checkUserInOrganization(TEST_USER_ID, TEST_ORG_ID);
|
||||
|
||||
expect(result).toEqual({
|
||||
|
@ -103,8 +101,6 @@ describe('user-organizations', () => {
|
|||
it('should return null when user does not exist in org', async () => {
|
||||
mockDb.limit.mockResolvedValue([]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await checkUserInOrganization(TEST_USER_ID, TEST_ORG_ID);
|
||||
|
||||
expect(result).toBeNull();
|
||||
|
@ -136,8 +132,6 @@ describe('user-organizations', () => {
|
|||
|
||||
mockDb.where.mockResolvedValue(mockOrgs);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await getUserOrganizations(TEST_USER_ID);
|
||||
|
||||
expect(result).toEqual(mockOrgs);
|
||||
|
@ -147,8 +141,6 @@ describe('user-organizations', () => {
|
|||
it('should return empty array when user has no organizations', async () => {
|
||||
mockDb.where.mockResolvedValue([]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await getUserOrganizations(TEST_USER_ID);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
|
@ -159,8 +151,6 @@ describe('user-organizations', () => {
|
|||
it('should return true when email domain matches org domain', async () => {
|
||||
mockDb.limit.mockResolvedValue([{ domains: ['example.com', 'test.com'] }]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await checkEmailDomainForOrganization('user@example.com', TEST_ORG_ID);
|
||||
|
||||
expect(result).toBe(true);
|
||||
|
@ -169,8 +159,6 @@ describe('user-organizations', () => {
|
|||
it('should return true when email domain matches with @ prefix', async () => {
|
||||
mockDb.limit.mockResolvedValue([{ domains: ['@example.com', 'test.com'] }]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await checkEmailDomainForOrganization('user@example.com', TEST_ORG_ID);
|
||||
|
||||
expect(result).toBe(true);
|
||||
|
@ -179,8 +167,6 @@ describe('user-organizations', () => {
|
|||
it('should return false when email domain does not match', async () => {
|
||||
mockDb.limit.mockResolvedValue([{ domains: ['other.com'] }]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await checkEmailDomainForOrganization('user@example.com', TEST_ORG_ID);
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
@ -189,8 +175,6 @@ describe('user-organizations', () => {
|
|||
it('should return false when org has no domains', async () => {
|
||||
mockDb.limit.mockResolvedValue([{ domains: null }]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await checkEmailDomainForOrganization('user@example.com', TEST_ORG_ID);
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
@ -199,8 +183,6 @@ describe('user-organizations', () => {
|
|||
it('should return false when org does not exist', async () => {
|
||||
mockDb.limit.mockResolvedValue([]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await checkEmailDomainForOrganization('user@example.com', TEST_ORG_ID);
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
@ -209,8 +191,6 @@ describe('user-organizations', () => {
|
|||
it('should be case insensitive for domain matching', async () => {
|
||||
mockDb.limit.mockResolvedValue([{ domains: ['EXAMPLE.COM'] }]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await checkEmailDomainForOrganization('user@example.com', TEST_ORG_ID);
|
||||
|
||||
expect(result).toBe(true);
|
||||
|
@ -230,8 +210,6 @@ describe('user-organizations', () => {
|
|||
|
||||
mockDb.limit.mockResolvedValue([mockOrg]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await getOrganizationWithDefaults(TEST_ORG_ID);
|
||||
|
||||
expect(result).toEqual(mockOrg);
|
||||
|
@ -240,8 +218,6 @@ describe('user-organizations', () => {
|
|||
it('should return null when organization does not exist', async () => {
|
||||
mockDb.limit.mockResolvedValue([]);
|
||||
|
||||
vi.mocked(db.getDb).mockResolvedValue(mockDb as any);
|
||||
|
||||
const result = await getOrganizationWithDefaults(TEST_ORG_ID);
|
||||
|
||||
expect(result).toBeNull();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
type User,
|
||||
and,
|
||||
dbInitialized,
|
||||
db,
|
||||
eq,
|
||||
isNull,
|
||||
organizations,
|
||||
|
@ -58,7 +58,6 @@ export async function checkUserInOrganization(
|
|||
organizationId: string
|
||||
): Promise<UserOrganizationInfo | null> {
|
||||
const input = CheckUserInOrganizationSchema.parse({ userId, organizationId });
|
||||
const db = await dbInitialized;
|
||||
|
||||
const result = await db
|
||||
.select({
|
||||
|
@ -91,7 +90,6 @@ export async function checkUserInOrganization(
|
|||
*/
|
||||
export async function getUserOrganizations(userId: string): Promise<UserOrganizationInfo[]> {
|
||||
const input = GetUserOrganizationsSchema.parse({ userId });
|
||||
const db = await dbInitialized;
|
||||
|
||||
const results = await db
|
||||
.select({
|
||||
|
@ -119,7 +117,6 @@ export async function checkEmailDomainForOrganization(
|
|||
organizationId: string
|
||||
): Promise<boolean> {
|
||||
const input = CheckEmailDomainSchema.parse({ email, organizationId });
|
||||
const db = await dbInitialized;
|
||||
|
||||
// Get organization domains
|
||||
const orgResult = await db
|
||||
|
@ -161,8 +158,6 @@ export async function checkEmailDomainForOrganization(
|
|||
export async function getOrganizationWithDefaults(
|
||||
organizationId: string
|
||||
): Promise<OrganizationWithDefaults | null> {
|
||||
const db = await dbInitialized;
|
||||
|
||||
const result = await db
|
||||
.select()
|
||||
.from(organizations)
|
||||
|
@ -191,7 +186,6 @@ export async function createUserInOrganization(
|
|||
createdById: string
|
||||
): Promise<{ user: User; membership: UserOrganizationInfo }> {
|
||||
const input = CreateUserInOrganizationSchema.parse({ email, name, organizationId, createdById });
|
||||
const db = await dbInitialized;
|
||||
|
||||
// Get organization defaults
|
||||
const org = await getOrganizationWithDefaults(input.organizationId);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { InvalidToolInputError, NoSuchToolError } from 'ai';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ANALYST_AGENT_NAME } from '../../agents';
|
||||
import { repairToolCall } from './repair-tool-call';
|
||||
import type { RepairContext } from './types';
|
||||
|
||||
vi.mock('braintrust', () => ({
|
||||
wrapTraced: (fn: any) => fn,
|
||||
}));
|
||||
// Set up environment variables before any imports to prevent initialization errors
|
||||
process.env.ANTHROPIC_API_KEY = 'test-anthropic-key';
|
||||
process.env.OPENAI_API_KEY = 'test-openai-key';
|
||||
process.env.VERTEX_CLIENT_EMAIL = 'test-vertex-email';
|
||||
process.env.VERTEX_PRIVATE_KEY = 'test-vertex-key';
|
||||
process.env.VERTEX_PROJECT = 'test-vertex-project';
|
||||
|
||||
// Mock the strategy functions
|
||||
vi.mock('./strategies/structured-output-strategy', () => ({
|
||||
|
@ -19,6 +19,11 @@ vi.mock('./strategies/re-ask-strategy', () => ({
|
|||
repairWrongToolName: vi.fn(),
|
||||
}));
|
||||
|
||||
// Import after all mocks are set up
|
||||
import { ANALYST_AGENT_NAME } from '../../agents';
|
||||
import { repairToolCall } from './repair-tool-call';
|
||||
import type { RepairContext } from './types';
|
||||
|
||||
describe('repairToolCall', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
|
|
@ -16,8 +16,20 @@ vi.mock('@buster/database', () => ({
|
|||
getSecretByName: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock secrets package
|
||||
vi.mock('@buster/secrets', () => ({
|
||||
getSecret: vi.fn(),
|
||||
DATA_SOURCE_KEYS: {
|
||||
R2_ACCOUNT_ID: 'R2_ACCOUNT_ID',
|
||||
R2_ACCESS_KEY_ID: 'R2_ACCESS_KEY_ID',
|
||||
R2_SECRET_ACCESS_KEY: 'R2_SECRET_ACCESS_KEY',
|
||||
R2_BUCKET: 'R2_BUCKET',
|
||||
},
|
||||
}));
|
||||
|
||||
// Import after mocking
|
||||
import { getS3IntegrationByOrganizationId, getSecretByName } from '@buster/database';
|
||||
import { getSecret } from '@buster/secrets';
|
||||
vi.mock('./providers/s3-provider');
|
||||
vi.mock('./providers/r2-provider');
|
||||
vi.mock('./providers/gcs-provider');
|
||||
|
@ -115,23 +127,18 @@ describe('Storage Factory', () => {
|
|||
});
|
||||
|
||||
describe('getDefaultProvider', () => {
|
||||
const originalEnv = process.env;
|
||||
it('should create default R2 provider with environment variables', async () => {
|
||||
(getSecret as Mock).mockImplementation(async (key: string) => {
|
||||
const secrets: Record<string, string> = {
|
||||
R2_ACCOUNT_ID: 'test-account',
|
||||
R2_ACCESS_KEY_ID: 'test-key',
|
||||
R2_SECRET_ACCESS_KEY: 'test-secret',
|
||||
R2_BUCKET: 'custom-bucket',
|
||||
};
|
||||
return secrets[key];
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
it('should create default R2 provider with environment variables', () => {
|
||||
process.env.R2_ACCOUNT_ID = 'test-account';
|
||||
process.env.R2_ACCESS_KEY_ID = 'test-key';
|
||||
process.env.R2_SECRET_ACCESS_KEY = 'test-secret';
|
||||
process.env.R2_BUCKET = 'custom-bucket';
|
||||
|
||||
const provider = getDefaultProvider();
|
||||
const provider = await getDefaultProvider();
|
||||
|
||||
expect(createR2Provider).toHaveBeenCalledWith({
|
||||
provider: 'r2',
|
||||
|
@ -143,55 +150,59 @@ describe('Storage Factory', () => {
|
|||
expect(provider).toBeDefined();
|
||||
});
|
||||
|
||||
it('should use default bucket name if not specified', () => {
|
||||
process.env.R2_ACCOUNT_ID = 'test-account';
|
||||
process.env.R2_ACCESS_KEY_ID = 'test-key';
|
||||
process.env.R2_SECRET_ACCESS_KEY = 'test-secret';
|
||||
delete process.env.R2_BUCKET;
|
||||
|
||||
const provider = getDefaultProvider();
|
||||
|
||||
expect(createR2Provider).toHaveBeenCalledWith({
|
||||
provider: 'r2',
|
||||
accountId: 'test-account',
|
||||
bucket: 'metric-exports',
|
||||
accessKeyId: 'test-key',
|
||||
secretAccessKey: 'test-secret',
|
||||
it('should use default bucket name if not specified', async () => {
|
||||
(getSecret as Mock).mockImplementation(async (key: string) => {
|
||||
const secrets: Record<string, string | undefined> = {
|
||||
R2_ACCOUNT_ID: 'test-account',
|
||||
R2_ACCESS_KEY_ID: 'test-key',
|
||||
R2_SECRET_ACCESS_KEY: 'test-secret',
|
||||
R2_BUCKET: undefined,
|
||||
};
|
||||
return secrets[key];
|
||||
});
|
||||
expect(provider).toBeDefined();
|
||||
|
||||
await expect(getDefaultProvider()).rejects.toThrow(
|
||||
'Default R2 storage credentials not configured'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error if R2 credentials are missing', () => {
|
||||
delete process.env.R2_ACCOUNT_ID;
|
||||
delete process.env.R2_ACCESS_KEY_ID;
|
||||
delete process.env.R2_SECRET_ACCESS_KEY;
|
||||
it('should throw error if R2 credentials are missing', async () => {
|
||||
(getSecret as Mock).mockResolvedValue(undefined);
|
||||
|
||||
expect(() => getDefaultProvider()).toThrow('Default R2 storage credentials not configured');
|
||||
await expect(getDefaultProvider()).rejects.toThrow(
|
||||
'Default R2 storage credentials not configured'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error if partial R2 credentials are missing', () => {
|
||||
process.env.R2_ACCOUNT_ID = 'test-account';
|
||||
delete process.env.R2_ACCESS_KEY_ID;
|
||||
process.env.R2_SECRET_ACCESS_KEY = 'test-secret';
|
||||
it('should throw error if partial R2 credentials are missing', async () => {
|
||||
(getSecret as Mock).mockImplementation(async (key: string) => {
|
||||
const secrets: Record<string, string | undefined> = {
|
||||
R2_ACCOUNT_ID: 'test-account',
|
||||
R2_ACCESS_KEY_ID: undefined,
|
||||
R2_SECRET_ACCESS_KEY: 'test-secret',
|
||||
R2_BUCKET: 'metric-exports',
|
||||
};
|
||||
return secrets[key];
|
||||
});
|
||||
|
||||
expect(() => getDefaultProvider()).toThrow('Default R2 storage credentials not configured');
|
||||
await expect(getDefaultProvider()).rejects.toThrow(
|
||||
'Default R2 storage credentials not configured'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProviderForOrganization', () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
R2_ACCOUNT_ID: 'default-account',
|
||||
R2_ACCESS_KEY_ID: 'default-key',
|
||||
R2_SECRET_ACCESS_KEY: 'default-secret',
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
// Mock getSecret for default provider
|
||||
(getSecret as Mock).mockImplementation(async (key: string) => {
|
||||
const secrets: Record<string, string> = {
|
||||
R2_ACCOUNT_ID: 'default-account',
|
||||
R2_ACCESS_KEY_ID: 'default-key',
|
||||
R2_SECRET_ACCESS_KEY: 'default-secret',
|
||||
R2_BUCKET: 'metric-exports',
|
||||
};
|
||||
return secrets[key];
|
||||
});
|
||||
});
|
||||
|
||||
it('should return S3 provider for organization with S3 integration', async () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { DATABASE_KEYS, SHARED_KEYS, getSecret } from '@buster/secrets';
|
||||
import { DATABASE_KEYS, SHARED_KEYS, getSecretSync } from '@buster/secrets';
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
|
@ -7,20 +7,20 @@ import postgres from 'postgres';
|
|||
let globalPool: postgres.Sql | null = null;
|
||||
let globalDb: PostgresJsDatabase | null = null;
|
||||
|
||||
// Helper to safely get secret
|
||||
async function getEnvValue(key: string, defaultValue?: string): Promise<string | undefined> {
|
||||
// Helper to safely get secret synchronously
|
||||
function getEnvValue(key: string, defaultValue?: string): string | undefined {
|
||||
try {
|
||||
return await getSecret(key);
|
||||
return getSecretSync(key);
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Environment validation
|
||||
async function validateEnvironment(): Promise<string> {
|
||||
const isTest = (await getEnvValue(SHARED_KEYS.NODE_ENV)) === 'test';
|
||||
const isProduction = (await getEnvValue(SHARED_KEYS.NODE_ENV)) === 'production';
|
||||
const dbUrl = await getEnvValue(DATABASE_KEYS.DATABASE_URL);
|
||||
// Environment validation (now synchronous)
|
||||
function validateEnvironment(): string {
|
||||
const isTest = getEnvValue(SHARED_KEYS.NODE_ENV) === 'test';
|
||||
const isProduction = getEnvValue(SHARED_KEYS.NODE_ENV) === 'production';
|
||||
const dbUrl = getEnvValue(DATABASE_KEYS.DATABASE_URL);
|
||||
|
||||
// Use default local database URL if none provided
|
||||
if (!dbUrl) {
|
||||
|
@ -30,7 +30,7 @@ async function validateEnvironment(): Promise<string> {
|
|||
}
|
||||
|
||||
// Prevent accidental production database usage in tests
|
||||
const allowProdInTests = await getEnvValue('ALLOW_PROD_DB_IN_TESTS'); // Not in constants - rarely used
|
||||
const allowProdInTests = getEnvValue('ALLOW_PROD_DB_IN_TESTS'); // Not in constants - rarely used
|
||||
if (isTest && dbUrl.includes('prod') && !allowProdInTests) {
|
||||
throw new Error(
|
||||
'Production database detected in test environment. Set ALLOW_PROD_DB_IN_TESTS=true to override.'
|
||||
|
@ -38,7 +38,7 @@ async function validateEnvironment(): Promise<string> {
|
|||
}
|
||||
|
||||
// Warn about non-pooled connections in production
|
||||
const poolSize = await getEnvValue('DATABASE_POOL_SIZE'); // Not in constants - optional config
|
||||
const poolSize = getEnvValue('DATABASE_POOL_SIZE'); // Not in constants - optional config
|
||||
if (isProduction && !poolSize) {
|
||||
console.warn('DATABASE_POOL_SIZE not set - using default pool size of 100');
|
||||
}
|
||||
|
@ -46,19 +46,18 @@ async function validateEnvironment(): Promise<string> {
|
|||
return dbUrl;
|
||||
}
|
||||
|
||||
// Initialize the database pool
|
||||
export async function initializePool<T extends Record<string, postgres.PostgresType>>(
|
||||
// Initialize the database pool (now synchronous)
|
||||
export function initializePool<T extends Record<string, postgres.PostgresType>>(
|
||||
config: postgres.Options<T> | undefined = {}
|
||||
): Promise<PostgresJsDatabase> {
|
||||
const connectionString = await validateEnvironment();
|
||||
|
||||
const poolSizeStr = await getEnvValue('DATABASE_POOL_SIZE'); // Not in constants - optional config
|
||||
const poolSize = poolSizeStr ? Number.parseInt(poolSizeStr) : 100;
|
||||
|
||||
): PostgresJsDatabase {
|
||||
if (globalPool && globalDb) {
|
||||
return globalDb;
|
||||
}
|
||||
|
||||
const connectionString = validateEnvironment();
|
||||
const poolSizeStr = getEnvValue('DATABASE_POOL_SIZE'); // Not in constants - optional config
|
||||
const poolSize = poolSizeStr ? Number.parseInt(poolSizeStr) : 100;
|
||||
|
||||
// Create postgres client with pool configuration
|
||||
globalPool = postgres(connectionString, {
|
||||
max: poolSize,
|
||||
|
@ -74,18 +73,18 @@ export async function initializePool<T extends Record<string, postgres.PostgresT
|
|||
return globalDb;
|
||||
}
|
||||
|
||||
// Get the database instance (initializes if not already done)
|
||||
export async function getDb(): Promise<PostgresJsDatabase> {
|
||||
// Get the database instance (initializes if not already done) - now synchronous
|
||||
export function getDb(): PostgresJsDatabase {
|
||||
if (!globalDb) {
|
||||
return await initializePool();
|
||||
return initializePool();
|
||||
}
|
||||
return globalDb;
|
||||
}
|
||||
|
||||
// Get the raw postgres client
|
||||
export async function getClient(): Promise<postgres.Sql> {
|
||||
// Get the raw postgres client (now synchronous)
|
||||
export function getClient(): postgres.Sql {
|
||||
if (!globalPool) {
|
||||
await initializePool();
|
||||
initializePool();
|
||||
}
|
||||
if (!globalPool) {
|
||||
throw new Error('Failed to initialize database pool');
|
||||
|
@ -105,7 +104,7 @@ export async function closePool(): Promise<void> {
|
|||
// Ping the database to check if connection is possible
|
||||
export async function dbPing(): Promise<boolean> {
|
||||
try {
|
||||
const client = await getClient();
|
||||
const client = getClient();
|
||||
await client`SELECT 1`;
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
@ -122,43 +121,5 @@ export function getSyncDb(): PostgresJsDatabase {
|
|||
return globalDb;
|
||||
}
|
||||
|
||||
// Export the database initialization promise
|
||||
export const dbInitialized = getDb();
|
||||
|
||||
// Auto-initialize database on module import for Trigger.dev tasks
|
||||
// This ensures the database is ready when the task runs
|
||||
getDb().catch((error) => {
|
||||
console.error('Failed to initialize database connection on module load:', error);
|
||||
// Don't throw - let individual queries handle the error
|
||||
});
|
||||
|
||||
// Export a synchronous database instance (will throw if not initialized)
|
||||
// This maintains backwards compatibility for existing code
|
||||
export const db = new Proxy({} as PostgresJsDatabase, {
|
||||
get(_target, prop) {
|
||||
if (!globalDb) {
|
||||
throw new Error(
|
||||
'Database not initialized. Import and await dbInitialized first, or use getSyncDb() after initialization.'
|
||||
);
|
||||
}
|
||||
return Reflect.get(globalDb, prop);
|
||||
},
|
||||
has(_target, prop) {
|
||||
if (!globalDb) {
|
||||
throw new Error('Database not initialized. Import and await dbInitialized first.');
|
||||
}
|
||||
return prop in globalDb;
|
||||
},
|
||||
ownKeys(_target) {
|
||||
if (!globalDb) {
|
||||
throw new Error('Database not initialized.');
|
||||
}
|
||||
return Reflect.ownKeys(globalDb);
|
||||
},
|
||||
getOwnPropertyDescriptor(_target, prop) {
|
||||
if (!globalDb) {
|
||||
throw new Error('Database not initialized.');
|
||||
}
|
||||
return Reflect.getOwnPropertyDescriptor(globalDb, prop);
|
||||
},
|
||||
});
|
||||
// Export the database instance - initializes synchronously on first use
|
||||
export const db: PostgresJsDatabase = getDb();
|
||||
|
|
|
@ -38,7 +38,7 @@ export type UpdateSecretInput = z.infer<typeof UpdateSecretInputSchema>;
|
|||
*/
|
||||
export async function createSecret(input: CreateSecretInput): Promise<string> {
|
||||
const validatedInput = CreateSecretInputSchema.parse(input);
|
||||
const client = await getClient();
|
||||
const client = getClient();
|
||||
|
||||
try {
|
||||
const result = await client`
|
||||
|
@ -66,7 +66,7 @@ export async function createSecret(input: CreateSecretInput): Promise<string> {
|
|||
*/
|
||||
export async function updateSecret(input: UpdateSecretInput): Promise<string> {
|
||||
const validatedInput = UpdateSecretInputSchema.parse(input);
|
||||
const client = await getClient();
|
||||
const client = getClient();
|
||||
|
||||
try {
|
||||
// Note: vault.update_secret returns void, not an ID
|
||||
|
@ -93,7 +93,7 @@ export async function updateSecret(input: UpdateSecretInput): Promise<string> {
|
|||
*/
|
||||
export async function deleteSecret(id: string): Promise<void> {
|
||||
const validatedId = z.string().uuid().parse(id);
|
||||
const client = await getClient();
|
||||
const client = getClient();
|
||||
|
||||
try {
|
||||
await client`
|
||||
|
@ -112,7 +112,7 @@ export async function deleteSecret(id: string): Promise<void> {
|
|||
*/
|
||||
export async function getSecret(id: string): Promise<VaultSecret | null> {
|
||||
const validatedId = z.string().uuid().parse(id);
|
||||
const client = await getClient();
|
||||
const client = getClient();
|
||||
|
||||
try {
|
||||
const result = await client`
|
||||
|
@ -147,7 +147,7 @@ export async function getSecret(id: string): Promise<VaultSecret | null> {
|
|||
*/
|
||||
export async function getSecretByName(name: string): Promise<VaultSecret | null> {
|
||||
const validatedName = z.string().parse(name);
|
||||
const client = await getClient();
|
||||
const client = getClient();
|
||||
|
||||
try {
|
||||
const result = await client`
|
||||
|
@ -182,7 +182,7 @@ export async function getSecretByName(name: string): Promise<VaultSecret | null>
|
|||
*/
|
||||
export async function listSecrets(limit = 100): Promise<VaultSecret[]> {
|
||||
const validatedLimit = z.number().positive().max(1000).parse(limit);
|
||||
const client = await getClient();
|
||||
const client = getClient();
|
||||
|
||||
try {
|
||||
const result = await client`
|
||||
|
|
|
@ -63,7 +63,7 @@ export async function searchValuesByEmbedding(
|
|||
const validOptions = SearchOptionsSchema.parse(options);
|
||||
|
||||
const schemaName = formatSchemaName(validDataSourceId);
|
||||
const client = await getClient();
|
||||
const client = getClient();
|
||||
|
||||
// Build similarity expression with optional threshold
|
||||
const similarityExpr = validOptions.similarityThreshold
|
||||
|
@ -158,7 +158,7 @@ export async function searchValuesByEmbeddingWithFilters(
|
|||
const validOptions = SearchOptionsSchema.parse(options);
|
||||
|
||||
const pgSchemaName = formatSchemaName(validDataSourceId);
|
||||
const client = await getClient();
|
||||
const client = getClient();
|
||||
|
||||
// Build filter conditions and parameters
|
||||
const filters: string[] = [];
|
||||
|
@ -445,7 +445,7 @@ export async function healthCheck(dataSourceId: string): Promise<boolean> {
|
|||
try {
|
||||
const validDataSourceId = UuidSchema.parse(dataSourceId);
|
||||
const schemaName = formatSchemaName(validDataSourceId);
|
||||
const client = await getClient();
|
||||
const client = getClient();
|
||||
|
||||
// Check if the table exists and is accessible
|
||||
const result = await client.unsafe(
|
||||
|
|
Loading…
Reference in New Issue