import { createClient, RealtimeChannel, SupabaseClient } from '../src/index.ts' // These tests assume that a local Supabase server is already running // Start a local Supabase instance with 'supabase start' before running these tests // Default local dev credentials from Supabase CLI const SUPABASE_URL = 'http://127.0.0.1:54321' const ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' // For Node.js < 22, we need to provide a WebSocket implementation // Node.js 22+ has native WebSocket support let wsTransport: any = undefined if (typeof WebSocket === 'undefined' && typeof process !== 'undefined' && process.versions?.node) { try { wsTransport = require('./ws') } catch (error) { console.warn('WebSocket not available, Realtime features may not work') } } const supabase = createClient(SUPABASE_URL, ANON_KEY, { realtime: { heartbeatIntervalMs: 500, ...(wsTransport && { transport: wsTransport }), }, }) describe('Supabase Integration Tests', () => { test('should connect to Supabase instance', async () => { expect(supabase).toBeDefined() expect(supabase).toBeInstanceOf(SupabaseClient) }) describe('PostgREST', () => { test('should connect to PostgREST API', async () => { const { data, error } = await supabase.from('todos').select('*').limit(5) // The default schema includes a 'todos' table, but it might be empty expect(error).toBeNull() expect(Array.isArray(data)).toBe(true) }) // Test creating and deleting data test('should create and delete a todo', async () => { // Create a new todo const { data: createdTodo, error: createError } = await supabase .from('todos') .insert({ task: 'Integration Test Todo', is_complete: false }) .select() .single() expect(createError).toBeNull() expect(createdTodo).toBeDefined() expect(createdTodo!.task).toBe('Integration Test Todo') expect(createdTodo!.is_complete).toBe(false) // Delete the created todo const { error: deleteError } = await supabase.from('todos').delete().eq('id', createdTodo!.id) expect(deleteError).toBeNull() // Verify the todo was deleted const { data: fetchedTodo, error: fetchError } = await supabase .from('todos') .select('*') .eq('id', createdTodo!.id) .single() expect(fetchError).not.toBeNull() expect(fetchedTodo).toBeNull() }) }) describe('PostgreSQL RLS', () => { let user1Email: string let user2Email: string let user1Id: string let user2Id: string let user1TodoId: string let user2TodoId: string beforeAll(async () => { // Create two test users user1Email = `user1-${Date.now()}@example.com` user2Email = `user2-${Date.now()}@example.com` const password = 'password123' const { data: user1Data } = await supabase.auth.signUp({ email: user1Email, password, }) user1Id = user1Data.user!.id const { data: user2Data } = await supabase.auth.signUp({ email: user2Email, password, }) user2Id = user2Data.user!.id // Create todos for both users await supabase.auth.signInWithPassword({ email: user1Email, password }) const { data: user1Todo } = await supabase .from('todos') .insert({ task: 'User 1 Todo', is_complete: false, user_id: user1Id }) .select() .single() user1TodoId = user1Todo!.id await supabase.auth.signInWithPassword({ email: user2Email, password }) const { data: user2Todo } = await supabase .from('todos') .insert({ task: 'User 2 Todo', is_complete: false, user_id: user2Id }) .select() .single() user2TodoId = user2Todo!.id }) afterAll(async () => { await supabase.auth.signOut() }) test('should allow anonymous access via RLS policies', async () => { await supabase.auth.signOut() const { data, error } = await supabase.from('todos').select('*').limit(5) expect(error).toBeNull() expect(Array.isArray(data)).toBe(true) }) test('should allow authenticated user to access their own data', async () => { await supabase.auth.signInWithPassword({ email: user1Email, password: 'password123' }) const { data, error } = await supabase .from('todos') .select('*') .eq('id', user1TodoId) .single() expect(error).toBeNull() expect(data).toBeDefined() expect(data!.task).toBe('User 1 Todo') }) test('should prevent access to other users data', async () => { await supabase.auth.signInWithPassword({ email: user1Email, password: 'password123' }) const { data, error } = await supabase .from('todos') .select('*') .eq('id', user2TodoId) .single() expect(error).not.toBeNull() expect(data).toBeNull() }) test('should allow authenticated user to create their own data', async () => { await supabase.auth.signInWithPassword({ email: user1Email, password: 'password123' }) const { data, error } = await supabase .from('todos') .insert({ task: 'New User 1 Todo', is_complete: false, user_id: user1Id }) .select() .single() expect(error).toBeNull() expect(data).toBeDefined() expect(data!.task).toBe('New User 1 Todo') }) test('should allow authenticated user to update their own data', async () => { await supabase.auth.signInWithPassword({ email: user1Email, password: 'password123' }) const { data, error } = await supabase .from('todos') .update({ task: 'Updated User 1 Todo' }) .eq('id', user1TodoId) .select() .single() expect(error).toBeNull() expect(data).toBeDefined() expect(data!.task).toBe('Updated User 1 Todo') }) }) describe('Authentication', () => { afterAll(async () => { // Clean up by signing out the user await supabase.auth.signOut() }) test('should sign up a user', async () => { const email = `test-${Date.now()}@example.com` const password = 'password123' const { data, error } = await supabase.auth.signUp({ email, password, }) expect(error).toBeNull() expect(data.user).toBeDefined() expect(data.user!.email).toBe(email) }) test('should sign in and out successfully', async () => { const email = `test-${Date.now()}@example.com` const password = 'password123' await supabase.auth.signUp({ email, password }) const { data, error } = await supabase.auth.signInWithPassword({ email, password }) expect(error).toBeNull() expect(data.user).toBeDefined() expect(data.user!.email).toBe(email) const { error: signOutError } = await supabase.auth.signOut() expect(signOutError).toBeNull() }) test('should get current user', async () => { const email = `test-${Date.now()}@example.com` const password = 'password123' await supabase.auth.signUp({ email, password }) await supabase.auth.signInWithPassword({ email, password }) const { data, error } = await supabase.auth.getUser() expect(error).toBeNull() expect(data.user).toBeDefined() expect(data.user!.email).toBe(email) }) test('should handle invalid credentials', async () => { const email = `test-${Date.now()}@example.com` const password = 'password123' await supabase.auth.signUp({ email, password }) const { data, error } = await supabase.auth.signInWithPassword({ email, password: 'wrongpassword', }) expect(error).not.toBeNull() expect(data.user).toBeNull() }) test('should handle non-existent user', async () => { const email = `nonexistent-${Date.now()}@example.com` const password = 'password123' const { data, error } = await supabase.auth.signInWithPassword({ email, password, }) expect(error).not.toBeNull() expect(data.user).toBeNull() }) }) describe('Realtime', () => { const channelName = `channel-${crypto.randomUUID()}` let channel: RealtimeChannel let email: string let password: string beforeEach(async () => { await supabase.auth.signOut() email = `test-${Date.now()}@example.com` password = 'password123' await supabase.auth.signUp({ email, password }) const config = { broadcast: { self: true }, private: true } channel = supabase.channel(channelName, { config }) }) afterEach(async () => { await supabase.removeAllChannels() }) test('is able to connect and broadcast', async () => { const testMessage = { message: 'test' } let receivedMessage: any let subscribed = false let attempts = 0 channel .on('broadcast', { event: '*' }, (payload) => (receivedMessage = payload)) .subscribe((status) => { if (status == 'SUBSCRIBED') subscribed = true }) // Wait for subscription while (!subscribed) { if (attempts > 50) throw new Error('Timeout waiting for subscription') await new Promise((resolve) => setTimeout(resolve, 100)) attempts++ } attempts = 0 channel.send({ type: 'broadcast', event: 'test-event', payload: testMessage }) // Wait on message while (!receivedMessage) { if (attempts > 50) throw new Error('Timeout waiting for message') await new Promise((resolve) => setTimeout(resolve, 100)) attempts++ } expect(receivedMessage).toBeDefined() expect(supabase.realtime.getChannels().length).toBe(1) }, 10000) }) }) describe('Storage API', () => { const bucket = 'test-bucket' const filePath = 'test-file.txt' const fileContent = new Blob(['Hello, Supabase Storage!'], { type: 'text/plain' }) // use service_role key for bypass RLS const SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || 'use-service-role-key' const supabaseWithServiceRole = createClient(SUPABASE_URL, SERVICE_ROLE_KEY, { realtime: { heartbeatIntervalMs: 500, ...(wsTransport && { transport: wsTransport }), }, }) test('upload and list file in bucket', async () => { // upload const { data: uploadData, error: uploadError } = await supabaseWithServiceRole.storage .from(bucket) .upload(filePath, fileContent, { upsert: true }) expect(uploadError).toBeNull() expect(uploadData).toBeDefined() // list const { data: listData, error: listError } = await supabaseWithServiceRole.storage .from(bucket) .list() expect(listError).toBeNull() expect(Array.isArray(listData)).toBe(true) if (!listData) throw new Error('listData is null') const fileNames = listData.map((f: any) => f.name) expect(fileNames).toContain('test-file.txt') // delete file const { error: deleteError } = await supabaseWithServiceRole.storage .from(bucket) .remove([filePath]) expect(deleteError).toBeNull() }) })