buster/apps/server/src/api/v2/electric-shape/_helpers/helpers.test.ts

318 lines
12 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { extractParamFromWhere, getElectricShapeUrl } from '.';
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 = '';
});
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;
}
});
it('should return default URL with /v1/shape path when no ELECTRIC_PROXY_URL is set', () => {
const requestUrl = 'http://example.com/test?table=users';
const result = getElectricShapeUrl(requestUrl);
expect(result.toString()).toBe('http://localhost:3000/v1/shape?table=users');
});
it('should use ELECTRIC_PROXY_URL environment variable when set', () => {
process.env.ELECTRIC_PROXY_URL = 'https://electric.example.com';
const requestUrl = 'http://example.com/test?table=users&live=true';
const result = getElectricShapeUrl(requestUrl);
expect(result.toString()).toBe('https://electric.example.com/v1/shape?table=users&live=true');
});
it('should copy allowed query parameters', () => {
const requestUrl = 'http://example.com/test?live=true&table=users&handle=abc123&offset=100';
const result = getElectricShapeUrl(requestUrl);
expect(result.searchParams.get('live')).toBe('true');
expect(result.searchParams.get('table')).toBe('users');
expect(result.searchParams.get('handle')).toBe('abc123');
expect(result.searchParams.get('offset')).toBe('100');
});
it('should copy all allowed query parameters', () => {
const allowedParams = [
'live=true',
'table=users',
'handle=abc123',
'offset=100',
'cursor=xyz789',
'where=id>10',
'params={"key":"value"}',
'columns=id,name',
'replica=primary',
'secret=secret123',
];
const requestUrl = `http://example.com/test?${allowedParams.join('&')}`;
const result = getElectricShapeUrl(requestUrl);
expect(result.searchParams.get('live')).toBe('true');
expect(result.searchParams.get('table')).toBe('users');
expect(result.searchParams.get('handle')).toBe('abc123');
expect(result.searchParams.get('offset')).toBe('100');
expect(result.searchParams.get('cursor')).toBe('xyz789');
expect(result.searchParams.get('where')).toBe('id>10');
expect(result.searchParams.get('params')).toBe('{"key":"value"}');
expect(result.searchParams.get('columns')).toBe('id,name');
expect(result.searchParams.get('replica')).toBe('primary');
expect(result.searchParams.get('secret')).toBe('secret123');
});
it('should not copy disallowed query parameters', () => {
const requestUrl = 'http://example.com/test?table=users&forbidden=value&another=param&limit=50';
const result = getElectricShapeUrl(requestUrl);
expect(result.searchParams.get('table')).toBe('users');
expect(result.searchParams.get('forbidden')).toBeNull();
expect(result.searchParams.get('another')).toBeNull();
expect(result.searchParams.get('limit')).toBeNull();
});
it('should handle mixed allowed and disallowed parameters', () => {
const requestUrl =
'http://example.com/test?live=true&forbidden=value&table=users&limit=50&handle=abc';
const result = getElectricShapeUrl(requestUrl);
expect(result.searchParams.get('live')).toBe('true');
expect(result.searchParams.get('table')).toBe('users');
expect(result.searchParams.get('handle')).toBe('abc');
expect(result.searchParams.get('forbidden')).toBeNull();
expect(result.searchParams.get('limit')).toBeNull();
});
it('should handle URL with no query parameters', () => {
const requestUrl = 'http://example.com/test';
const result = getElectricShapeUrl(requestUrl);
expect(result.toString()).toBe('http://localhost:3000/v1/shape');
expect(result.searchParams.toString()).toBe('');
});
it('should handle empty query parameter values', () => {
const requestUrl = 'http://example.com/test?table=&live=true&handle=';
const result = getElectricShapeUrl(requestUrl);
expect(result.searchParams.get('table')).toBe('');
expect(result.searchParams.get('live')).toBe('true');
expect(result.searchParams.get('handle')).toBe('');
});
it('should handle URL-encoded query parameters', () => {
const requestUrl = 'http://example.com/test?where=id%3E10&params=%7B%22key%22%3A%22value%22%7D';
const result = getElectricShapeUrl(requestUrl);
expect(result.searchParams.get('where')).toBe('id>10');
expect(result.searchParams.get('params')).toBe('{"key":"value"}');
});
it('should handle complex where clause with special characters', () => {
const whereClause = "name='John Doe' AND age>18";
const encodedWhere = encodeURIComponent(whereClause);
const requestUrl = `http://example.com/test?table=users&where=${encodedWhere}`;
const result = getElectricShapeUrl(requestUrl);
expect(result.searchParams.get('table')).toBe('users');
expect(result.searchParams.get('where')).toBe(whereClause);
});
it('should preserve multiple values for the same parameter', () => {
// Note: URL constructor handles multiple params by taking the last value
const requestUrl = 'http://example.com/test?table=users&table=posts';
const result = getElectricShapeUrl(requestUrl);
expect(result.searchParams.get('table')).toBe('posts');
});
it('should handle ELECTRIC_PROXY_URL with trailing slash', () => {
process.env.ELECTRIC_PROXY_URL = 'https://electric.example.com/';
const requestUrl = 'http://example.com/test?table=users';
const result = getElectricShapeUrl(requestUrl);
expect(result.toString()).toBe('https://electric.example.com/v1/shape?table=users');
});
it('should handle ELECTRIC_PROXY_URL with path', () => {
process.env.ELECTRIC_PROXY_URL = 'https://api.example.com/electric';
const requestUrl = 'http://example.com/test?table=users';
const result = getElectricShapeUrl(requestUrl);
expect(result.toString()).toBe('https://api.example.com/v1/shape?table=users');
});
});
describe('extractParamFromWhere', () => {
it('should handle a simple where clause', () => {
const testClause = "where=id='420226c8-b91d-49c5-99f8-660b04cc8c01'&offset=-1";
const url = new URL(`https://example.com/test?${testClause}`);
const result = extractParamFromWhere(url, 'id');
expect(result).not.toBeNull();
expect(result).toBe('420226c8-b91d-49c5-99f8-660b04cc8c01');
});
it('should extract parameter with single quotes', () => {
const url = new URL("https://example.com/test?where=chatId='123'");
const result = extractParamFromWhere(url, 'chatId');
expect(result).not.toBeNull();
expect(result).toBe('123');
});
it('should extract parameter with double quotes', () => {
const url = new URL('https://example.com/test?where=userId="456"');
const result = extractParamFromWhere(url, 'userId');
expect(result).not.toBeNull();
expect(result).toBe('456');
});
it('should return null when where parameter is missing', () => {
const url = new URL('https://example.com/test?table=users');
const result = extractParamFromWhere(url, 'chatId');
expect(result).toBeNull();
});
it('should return null when parameter name is not found in where clause', () => {
const url = new URL("https://example.com/test?where=otherId='123'");
const result = extractParamFromWhere(url, 'chatId');
expect(result).toBeNull();
});
it('should extract parameter from complex where clause', () => {
const url = new URL(
"https://example.com/test?where=status='active' AND chatId='789' AND type='message'"
);
const result = extractParamFromWhere(url, 'chatId');
expect(result).not.toBeNull();
expect(result).toBe('789');
});
it('should extract parameter with special characters in value', () => {
const url = new URL("https://example.com/test?where=chatId='abc-123_xyz'");
const result = extractParamFromWhere(url, 'chatId');
expect(result).not.toBeNull();
expect(result).toBe('abc-123_xyz');
});
it('should extract parameter with numeric value', () => {
const url = new URL("https://example.com/test?where=userId='12345'");
const result = extractParamFromWhere(url, 'userId');
expect(result).not.toBeNull();
expect(result).toBe('12345');
});
it('should handle empty where clause', () => {
const url = new URL('https://example.com/test?where=');
const result = extractParamFromWhere(url, 'chatId');
expect(result).toBeNull();
});
it('should extract first occurrence when parameter appears multiple times', () => {
const url = new URL("https://example.com/test?where=chatId='first' AND chatId='second'");
const result = extractParamFromWhere(url, 'chatId');
expect(result).not.toBeNull();
expect(result).toBe('first');
});
it('should handle URL-encoded where clause', () => {
const whereClause = "chatId='test123'";
const encodedWhere = encodeURIComponent(whereClause);
const url = new URL(`https://example.com/test?where=${encodedWhere}`);
const result = extractParamFromWhere(url, 'chatId');
expect(result).not.toBeNull();
expect(result).toBe('test123');
});
it('should handle parameter with spaces in value', () => {
const url = new URL("https://example.com/test?where=name='John Doe'");
const result = extractParamFromWhere(url, 'name');
expect(result).not.toBeNull();
expect(result).toBe('John Doe');
});
it('should handle parameter with empty value', () => {
const url = new URL("https://example.com/test?where=chatId=''");
const result = extractParamFromWhere(url, 'chatId');
expect(result).toBeNull(); // Empty string doesn't match the regex [^'"]+
});
it('should handle mixed quote types in same where clause', () => {
const url = new URL(`https://example.com/test?where=chatId='123' AND userId="456"`);
const result1 = extractParamFromWhere(url, 'chatId');
const result2 = extractParamFromWhere(url, 'userId');
expect(result1).not.toBeNull();
expect(result1).toBe('123');
expect(result2).not.toBeNull();
expect(result2).toBe('456');
});
it('should handle multiple paramater in a realistic example', () => {
const params = 'id="420226c8-b91d-49c5-99f8-660b04cc8c01"ANDmessage_id="123"';
const url = new URL(`https://example.com/test?where=${params}`);
const result = extractParamFromWhere(url, 'id');
expect(result).not.toBeNull();
expect(result).toBe('420226c8-b91d-49c5-99f8-660b04cc8c01');
const result2 = extractParamFromWhere(url, 'message_id');
expect(result2).not.toBeNull();
expect(result2).toBe('123');
});
it('should handle parameter names that are substrings of other parameters', () => {
const url = new URL("https://example.com/test?where=chatId='123' AND chatIdExtra='456'");
const result = extractParamFromWhere(url, 'chatId');
expect(result).not.toBeNull();
expect(result).toBe('123');
});
it('should handle parameter with UUID value', () => {
const uuid = 'f47ac10b-58cc-4372-a567-0e02b2c3d479';
const url = new URL(`https://example.com/test?where=chatId='${uuid}'`);
const result = extractParamFromWhere(url, 'chatId');
expect(result).not.toBeNull();
expect(result).toBe(uuid);
});
});