feat: migrate to docker-compose with selfhosted-supabase-mcp
- 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
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* Shared test mocks and helpers for the selfhosted-supabase-mcp test suite.
|
||||
*/
|
||||
|
||||
import { mock } from 'bun:test';
|
||||
import type { SelfhostedSupabaseClient } from '../../client/index.js';
|
||||
import type { ToolContext } from '../../tools/types.js';
|
||||
import type { SqlExecutionResult, SqlSuccessResponse, SqlErrorResponse } from '../../types/index.js';
|
||||
|
||||
/**
|
||||
* Options for creating a mock SelfhostedSupabaseClient
|
||||
*/
|
||||
export interface MockClientOptions {
|
||||
pgAvailable?: boolean;
|
||||
serviceRoleAvailable?: boolean;
|
||||
rpcResult?: SqlExecutionResult;
|
||||
pgResult?: SqlExecutionResult;
|
||||
serviceRoleRpcResult?: SqlExecutionResult;
|
||||
supabaseUrl?: string;
|
||||
anonKey?: string;
|
||||
serviceRoleKey?: string;
|
||||
jwtSecret?: string;
|
||||
dbUrl?: string;
|
||||
supabaseClient?: MockSupabaseClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock Supabase client type for auth operations
|
||||
*/
|
||||
export interface MockSupabaseClient {
|
||||
auth: {
|
||||
admin: {
|
||||
listUsers: ReturnType<typeof mock>;
|
||||
getUserById: ReturnType<typeof mock>;
|
||||
createUser: ReturnType<typeof mock>;
|
||||
updateUserById: ReturnType<typeof mock>;
|
||||
deleteUser: ReturnType<typeof mock>;
|
||||
};
|
||||
};
|
||||
rpc: ReturnType<typeof mock>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock Supabase client with configurable auth admin methods
|
||||
*/
|
||||
export function createMockSupabaseClient(overrides?: Partial<MockSupabaseClient>): MockSupabaseClient {
|
||||
return {
|
||||
auth: {
|
||||
admin: {
|
||||
listUsers: mock(() => Promise.resolve({ data: { users: [] }, error: null })),
|
||||
getUserById: mock(() => Promise.resolve({ data: { user: null }, error: null })),
|
||||
createUser: mock(() => Promise.resolve({ data: { user: null }, error: null })),
|
||||
updateUserById: mock(() => Promise.resolve({ data: { user: null }, error: null })),
|
||||
deleteUser: mock(() => Promise.resolve({ data: null, error: null })),
|
||||
...overrides?.auth?.admin,
|
||||
},
|
||||
},
|
||||
rpc: mock(() => Promise.resolve({ data: [], error: null })),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock SelfhostedSupabaseClient for testing tools
|
||||
*/
|
||||
export function createMockClient(options: MockClientOptions = {}): SelfhostedSupabaseClient {
|
||||
const {
|
||||
pgAvailable = true,
|
||||
serviceRoleAvailable = true,
|
||||
rpcResult = [] as SqlSuccessResponse,
|
||||
pgResult = [] as SqlSuccessResponse,
|
||||
serviceRoleRpcResult = [] as SqlSuccessResponse,
|
||||
supabaseUrl = 'https://test.supabase.co',
|
||||
anonKey = 'test-anon-key',
|
||||
serviceRoleKey = 'test-service-role-key',
|
||||
jwtSecret = 'test-jwt-secret',
|
||||
dbUrl = 'postgresql://test:test@localhost:5432/test',
|
||||
supabaseClient = createMockSupabaseClient(),
|
||||
} = options;
|
||||
|
||||
// Create a mock that satisfies the SelfhostedSupabaseClient interface
|
||||
const mockClient = {
|
||||
supabase: supabaseClient,
|
||||
|
||||
executeSqlViaRpc: mock(async (_query: string, _readOnly?: boolean) => rpcResult),
|
||||
executeSqlWithPg: mock(async (_query: string) => pgResult),
|
||||
executeSqlViaServiceRoleRpc: mock(async (_query: string, _readOnly?: boolean) => serviceRoleRpcResult),
|
||||
executeTransactionWithPg: mock(async <T>(callback: (client: unknown) => Promise<T>) => {
|
||||
const mockPgClient = {
|
||||
query: mock(() => Promise.resolve({ rows: [] })),
|
||||
};
|
||||
return callback(mockPgClient);
|
||||
}),
|
||||
|
||||
isPgAvailable: () => pgAvailable,
|
||||
isServiceRoleAvailable: () => serviceRoleAvailable,
|
||||
getSupabaseUrl: () => supabaseUrl,
|
||||
getAnonKey: () => anonKey,
|
||||
getServiceRoleKey: () => (serviceRoleKey ? serviceRoleKey : undefined),
|
||||
getJwtSecret: () => (jwtSecret ? jwtSecret : undefined),
|
||||
getDbUrl: () => (pgAvailable ? dbUrl : undefined),
|
||||
} as unknown as SelfhostedSupabaseClient;
|
||||
|
||||
return mockClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock ToolContext for testing tool execute functions
|
||||
*/
|
||||
export function createMockContext(client?: SelfhostedSupabaseClient): ToolContext {
|
||||
return {
|
||||
selfhostedClient: client ?? createMockClient(),
|
||||
log: mock((_message: string, _level?: 'info' | 'warn' | 'error') => {}),
|
||||
workspacePath: '/test/workspace',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SQL success response
|
||||
*/
|
||||
export function createSuccessResponse(rows: Record<string, unknown>[]): SqlSuccessResponse {
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SQL error response
|
||||
*/
|
||||
export function createErrorResponse(
|
||||
message: string,
|
||||
code?: string,
|
||||
details?: string,
|
||||
hint?: string
|
||||
): SqlErrorResponse {
|
||||
return {
|
||||
error: {
|
||||
message,
|
||||
code,
|
||||
details,
|
||||
hint,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample test data for various entity types
|
||||
*/
|
||||
export const testData = {
|
||||
users: [
|
||||
{
|
||||
id: '550e8400-e29b-41d4-a716-446655440001',
|
||||
email: '[email protected]',
|
||||
role: 'authenticated',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
last_sign_in_at: '2024-01-15T12:00:00Z',
|
||||
raw_app_meta_data: { provider: 'email' },
|
||||
raw_user_meta_data: { name: 'Test User 1' },
|
||||
},
|
||||
{
|
||||
id: '550e8400-e29b-41d4-a716-446655440002',
|
||||
email: '[email protected]',
|
||||
role: 'authenticated',
|
||||
created_at: '2024-01-02T00:00:00Z',
|
||||
last_sign_in_at: null,
|
||||
raw_app_meta_data: {},
|
||||
raw_user_meta_data: {},
|
||||
},
|
||||
],
|
||||
|
||||
tables: [
|
||||
{
|
||||
table_schema: 'public',
|
||||
table_name: 'users',
|
||||
table_type: 'BASE TABLE',
|
||||
is_insertable_into: 'YES',
|
||||
},
|
||||
{
|
||||
table_schema: 'public',
|
||||
table_name: 'posts',
|
||||
table_type: 'BASE TABLE',
|
||||
is_insertable_into: 'YES',
|
||||
},
|
||||
],
|
||||
|
||||
extensions: [
|
||||
{ name: 'plpgsql', installed_version: '1.0', comment: 'PL/pgSQL procedural language' },
|
||||
{ name: 'uuid-ossp', installed_version: '1.1', comment: 'generate universally unique identifiers' },
|
||||
],
|
||||
|
||||
buckets: [
|
||||
{
|
||||
id: 'bucket-1',
|
||||
name: 'avatars',
|
||||
owner: null,
|
||||
public: true,
|
||||
avif_autodetection: false,
|
||||
file_size_limit: 5242880,
|
||||
allowed_mime_types: ['image/png', 'image/jpeg'],
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
],
|
||||
|
||||
storageObjects: [
|
||||
{
|
||||
id: '550e8400-e29b-41d4-a716-446655440003',
|
||||
name: 'avatar.png',
|
||||
bucket_id: 'avatars',
|
||||
owner: '550e8400-e29b-41d4-a716-446655440001',
|
||||
version: null,
|
||||
mimetype: 'image/png',
|
||||
size: 1024,
|
||||
metadata: { mimetype: 'image/png', size: 1024 },
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
last_accessed_at: null,
|
||||
},
|
||||
],
|
||||
|
||||
migrations: [
|
||||
{
|
||||
version: '20240101000000',
|
||||
name: 'initial_schema',
|
||||
executed_at: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
],
|
||||
|
||||
connections: [
|
||||
{
|
||||
pid: 12345,
|
||||
usename: 'postgres',
|
||||
datname: 'postgres',
|
||||
client_addr: '127.0.0.1',
|
||||
state: 'active',
|
||||
query: 'SELECT 1',
|
||||
backend_start: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to create Express-like request/response mocks for middleware testing
|
||||
*/
|
||||
export function createMockExpressReqRes() {
|
||||
const req = {
|
||||
headers: {} as Record<string, string>,
|
||||
user: undefined as unknown,
|
||||
};
|
||||
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
jsonBody: null as unknown,
|
||||
status: mock(function(this: typeof res, code: number) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
}),
|
||||
json: mock(function(this: typeof res, body: unknown) {
|
||||
this.jsonBody = body;
|
||||
return this;
|
||||
}),
|
||||
};
|
||||
|
||||
const next = mock(() => {});
|
||||
|
||||
return { req, res, next };
|
||||
}
|
||||
Reference in New Issue
Block a user