- Replace legacy directory structure (api/, db/, functions/, logs/, pooler/) with single docker-compose.yml based self-hosted setup - Add selfhosted-supabase-mcp TypeScript MCP server for database management - Add .dockerignore for Docker build context - Update .gitignore to exclude .env files, volumes/, backups, logs
254 lines
8.9 KiB
TypeScript
254 lines
8.9 KiB
TypeScript
/**
|
|
* Tests for miscellaneous tools
|
|
*
|
|
* Tools tested:
|
|
* - get_project_url
|
|
* - verify_jwt_secret
|
|
* - generate_typescript_types
|
|
* - list_realtime_publications
|
|
* - list_cron_jobs
|
|
* - list_vector_indexes
|
|
*/
|
|
|
|
import { describe, test, expect, mock } from 'bun:test';
|
|
import { getProjectUrlTool } from '../../tools/get_project_url.js';
|
|
import { verifyJwtSecretTool } from '../../tools/verify_jwt_secret.js';
|
|
import { generateTypesTool } from '../../tools/generate_typescript_types.js';
|
|
import {
|
|
createMockClient,
|
|
createMockContext,
|
|
} from '../helpers/mocks.js';
|
|
|
|
describe('getProjectUrlTool', () => {
|
|
describe('metadata', () => {
|
|
test('has correct name', () => {
|
|
expect(getProjectUrlTool.name).toBe('get_project_url');
|
|
});
|
|
|
|
test('has description', () => {
|
|
expect(getProjectUrlTool.description).toContain('URL');
|
|
});
|
|
});
|
|
|
|
describe('input validation', () => {
|
|
test('accepts empty input', () => {
|
|
const result = getProjectUrlTool.inputSchema.safeParse({});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('execute', () => {
|
|
test('returns project URL', async () => {
|
|
const mockClient = createMockClient({
|
|
supabaseUrl: 'https://my-project.supabase.co',
|
|
});
|
|
const context = createMockContext(mockClient);
|
|
|
|
const result = await getProjectUrlTool.execute({}, context);
|
|
|
|
expect(result.project_url).toBe('https://my-project.supabase.co');
|
|
});
|
|
|
|
test('returns configured URL from client', async () => {
|
|
const customUrl = 'https://custom.supabase.example.com';
|
|
const mockClient = createMockClient({ supabaseUrl: customUrl });
|
|
const context = createMockContext(mockClient);
|
|
|
|
const result = await getProjectUrlTool.execute({}, context);
|
|
|
|
expect(result.project_url).toBe(customUrl);
|
|
});
|
|
});
|
|
|
|
describe('output validation', () => {
|
|
test('validates URL format', () => {
|
|
const result = getProjectUrlTool.outputSchema.safeParse({
|
|
project_url: 'https://example.com',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
test('rejects invalid URL', () => {
|
|
const result = getProjectUrlTool.outputSchema.safeParse({
|
|
project_url: 'not-a-url',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('verifyJwtSecretTool', () => {
|
|
describe('metadata', () => {
|
|
test('has correct name', () => {
|
|
expect(verifyJwtSecretTool.name).toBe('verify_jwt_secret');
|
|
});
|
|
|
|
test('has description about JWT', () => {
|
|
expect(verifyJwtSecretTool.description).toContain('JWT');
|
|
});
|
|
});
|
|
|
|
describe('execute', () => {
|
|
test('returns found status when JWT secret is configured (no preview for security)', async () => {
|
|
const mockClient = createMockClient({ jwtSecret: 'my-secret-jwt-key-12345' });
|
|
const context = createMockContext(mockClient);
|
|
|
|
const result = await verifyJwtSecretTool.execute({}, context);
|
|
|
|
expect(result.jwt_secret_status).toBe('found');
|
|
// SECURITY: jwt_secret_preview was removed to avoid leaking secret info
|
|
expect('jwt_secret_preview' in result).toBe(false);
|
|
});
|
|
|
|
test('returns not_configured status when JWT secret is missing', async () => {
|
|
const mockClient = createMockClient({ jwtSecret: undefined });
|
|
mockClient.getJwtSecret = () => undefined;
|
|
const context = createMockContext(mockClient);
|
|
|
|
const result = await verifyJwtSecretTool.execute({}, context);
|
|
|
|
expect(result.jwt_secret_status).toBe('not_configured');
|
|
});
|
|
});
|
|
|
|
describe('output validation', () => {
|
|
test('validates found status', () => {
|
|
const result = verifyJwtSecretTool.outputSchema.safeParse({
|
|
jwt_secret_status: 'found',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
test('validates not_configured status', () => {
|
|
const result = verifyJwtSecretTool.outputSchema.safeParse({
|
|
jwt_secret_status: 'not_configured',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
test('rejects invalid status', () => {
|
|
const result = verifyJwtSecretTool.outputSchema.safeParse({
|
|
jwt_secret_status: 'invalid',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('generateTypesTool', () => {
|
|
describe('metadata', () => {
|
|
test('has correct name', () => {
|
|
expect(generateTypesTool.name).toBe('generate_typescript_types');
|
|
});
|
|
|
|
test('has description about TypeScript types', () => {
|
|
expect(generateTypesTool.description).toContain('TypeScript');
|
|
});
|
|
});
|
|
|
|
describe('input validation', () => {
|
|
test('requires output_path', () => {
|
|
const result = generateTypesTool.inputSchema.safeParse({});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
test('accepts valid input', () => {
|
|
const result = generateTypesTool.inputSchema.safeParse({
|
|
output_path: '/path/to/types.ts',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
test('defaults included_schemas to public', () => {
|
|
const result = generateTypesTool.inputSchema.safeParse({
|
|
output_path: '/path/to/types.ts',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.included_schemas).toEqual(['public']);
|
|
}
|
|
});
|
|
|
|
test('accepts custom schemas', () => {
|
|
const result = generateTypesTool.inputSchema.safeParse({
|
|
output_path: '/path/to/types.ts',
|
|
included_schemas: ['public', 'auth', 'storage'],
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.included_schemas).toEqual(['public', 'auth', 'storage']);
|
|
}
|
|
});
|
|
|
|
test('defaults output_filename', () => {
|
|
const result = generateTypesTool.inputSchema.safeParse({
|
|
output_path: '/path/to/types.ts',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.output_filename).toBe('database.types.ts');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('execute', () => {
|
|
test('returns error when DATABASE_URL is not configured', async () => {
|
|
const mockClient = createMockClient({ dbUrl: undefined });
|
|
mockClient.getDbUrl = () => undefined;
|
|
const context = createMockContext(mockClient);
|
|
|
|
const result = await generateTypesTool.execute(
|
|
{ output_path: '/tmp/types.ts' },
|
|
context
|
|
);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.message).toContain('DATABASE_URL');
|
|
});
|
|
|
|
test('includes platform in response', async () => {
|
|
const mockClient = createMockClient({ dbUrl: undefined });
|
|
mockClient.getDbUrl = () => undefined;
|
|
const context = createMockContext(mockClient);
|
|
|
|
const result = await generateTypesTool.execute(
|
|
{ output_path: '/tmp/types.ts' },
|
|
context
|
|
);
|
|
|
|
expect(result.platform).toBeDefined();
|
|
expect(['win32', 'darwin', 'linux', 'freebsd', 'openbsd']).toContain(result.platform);
|
|
});
|
|
});
|
|
|
|
describe('output validation', () => {
|
|
test('validates success response', () => {
|
|
const result = generateTypesTool.outputSchema.safeParse({
|
|
success: true,
|
|
message: 'Types generated',
|
|
types: 'export type User = {...}',
|
|
file_path: '/path/to/types.ts',
|
|
platform: 'linux',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
test('validates failure response', () => {
|
|
const result = generateTypesTool.outputSchema.safeParse({
|
|
success: false,
|
|
message: 'Failed to generate types',
|
|
platform: 'darwin',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
test('requires platform field', () => {
|
|
const result = generateTypesTool.outputSchema.safeParse({
|
|
success: false,
|
|
message: 'Error',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
});
|