import { PostgrestClient } from 'npm:@supabase/postgrest-js@1.21.4' import { createClient, SupabaseClient } from '../../src/index.ts' import { Database } from '../types.ts' const URL = 'http://localhost:3000' const KEY = 'some.fake.key' describe('SupabaseClient', () => { test('it should create a client with third-party auth accessToken', async () => { const client = createClient(URL, KEY, { accessToken: async () => { return 'jwt' }, }) expect(() => client.auth.getUser()).toThrow( '@supabase/supabase-js: Supabase Client is configured with the accessToken option, accessing supabase.auth.getUser is not possible' ) }) test('it should create the client connection', async () => { const supabase = createClient(URL, KEY) expect(supabase).toBeDefined() expect(supabase).toBeInstanceOf(SupabaseClient) }) test('it should throw an error if no valid params are provided', async () => { expect(() => createClient('', KEY)).toThrow('supabaseUrl is required.') expect(() => createClient(URL, '')).toThrow('supabaseKey is required.') }) test('should validate supabaseUrl', () => { expect(() => createClient('https://xyz123.supabase.co', KEY)).not.toThrow() expect(() => createClient('http://localhost:54321', KEY)).not.toThrow() expect(() => createClient('http://[invalid', KEY)).toThrow( 'Invalid supabaseUrl: Provided URL is malformed.' ) expect(() => createClient('postgresql://postgre:password@db.test.co:5432/postgres', KEY) ).toThrow('Invalid supabaseUrl: Must be a valid HTTP or HTTPS URL.') expect(() => createClient('http:/localhost:3000', KEY)).toThrow( 'Invalid supabaseUrl: Must be a valid HTTP or HTTPS URL.' ) expect(() => createClient(' https://xyz123.supabase.co ', KEY)).not.toThrow() expect(() => createClient('http://user:pass@localhost:54321', KEY)).not.toThrow() }) describe('URL Construction', () => { test('should construct URLs correctly', () => { const client = createClient(URL, KEY) // @ts-ignore expect(client.authUrl.toString()).toEqual('http://localhost:3000/auth/v1') // @ts-ignore expect(client.realtimeUrl.toString()).toEqual('ws://localhost:3000/realtime/v1') // @ts-ignore expect(client.storageUrl.toString()).toEqual('http://localhost:3000/storage/v1') // @ts-ignore expect(client.functionsUrl.toString()).toEqual('http://localhost:3000/functions/v1') // @ts-ignore expect(client.rest.url).toEqual('http://localhost:3000/rest/v1') }) test('should preserve paths in supabaseUrl', () => { const baseUrlWithPath = 'http://localhost:3000/custom/base' const client = createClient(baseUrlWithPath, KEY) // @ts-ignore expect(client.authUrl.toString()).toEqual('http://localhost:3000/custom/base/auth/v1') // @ts-ignore expect(client.realtimeUrl.toString()).toEqual('ws://localhost:3000/custom/base/realtime/v1') // @ts-ignore expect(client.storageUrl.toString()).toEqual('http://localhost:3000/custom/base/storage/v1') // @ts-ignore expect(client.functionsUrl.toString()).toEqual( 'http://localhost:3000/custom/base/functions/v1' ) // @ts-ignore expect(client.rest.url).toEqual('http://localhost:3000/custom/base/rest/v1') }) test('should handle HTTPS URLs correctly', () => { const client = createClient('https://localhost:3000', KEY) // @ts-ignore expect(client.realtimeUrl.toString()).toEqual('wss://localhost:3000/realtime/v1') }) }) describe('Custom Headers', () => { test('should have custom header set', () => { const customHeader = { 'X-Test-Header': 'value' } const request = createClient(URL, KEY, { global: { headers: customHeader } }).rpc('') //@ts-expect-error headers is protected attribute const requestHeader = request.headers.get('X-Test-Header') expect(requestHeader).toBe(customHeader['X-Test-Header']) }) test('should merge custom headers with default headers', () => { const customHeader = { 'X-Test-Header': 'value' } const request = createClient(URL, KEY, { global: { headers: customHeader } }).rpc('') //@ts-expect-error headers is protected attribute const requestHeader = request.headers.get('X-Test-Header') expect(requestHeader).toBe(customHeader['X-Test-Header']) //@ts-expect-error headers is protected attribute expect(request.headers.get('X-Client-Info')).not.toBeNull() }) }) describe('Storage Key', () => { test('should use default storage key based on project ref', () => { const client = createClient('https://project-ref.supabase.co', KEY) // @ts-ignore expect(client.storageKey).toBe('sb-project-ref-auth-token') }) test('should use custom storage key when provided', () => { const customStorageKey = 'custom-storage-key' const client = createClient(URL, KEY, { auth: { storageKey: customStorageKey }, }) // @ts-ignore expect(client.storageKey).toBe(customStorageKey) }) test('should handle undefined storageKey and headers', () => { const client = createClient(URL, KEY, { auth: { storageKey: undefined }, global: { headers: undefined }, }) expect(client).toBeDefined() // @ts-ignore expect(client.storageKey).toBe('') // @ts-ignore expect(client.headers).toHaveProperty('X-Client-Info') }) }) describe('Client Methods', () => { test('should initialize functions client', () => { const client = createClient(URL, KEY) const functions = client.functions expect(functions).toBeDefined() // @ts-ignore expect(functions.url).toBe('http://localhost:3000/functions/v1') }) test('should initialize storage client', () => { const client = createClient(URL, KEY) const storage = client.storage expect(storage).toBeDefined() // @ts-ignore expect(storage.url).toBe('http://localhost:3000/storage/v1') }) test('should initialize realtime client', () => { const client = createClient(URL, KEY) expect(client.realtime).toBeDefined() // @ts-ignore expect(client.realtime.endPoint).toBe('ws://localhost:3000/realtime/v1/websocket') }) }) describe('Realtime Channel Management', () => { test('should create and manage channels', () => { const client = createClient(URL, KEY) const channel = client.channel('test-channel') expect(channel).toBeDefined() expect(client.getChannels()).toHaveLength(1) }) test('should remove channel', async () => { const client = createClient(URL, KEY) const channel = client.channel('test-channel') const result = await client.removeChannel(channel) expect(result).toBe('ok') expect(client.getChannels()).toHaveLength(0) }) test('should remove all channels', async () => { const client = createClient(URL, KEY) client.channel('channel1') client.channel('channel2') const results = await client.removeAllChannels() expect(results).toEqual(['ok', 'ok']) expect(client.getChannels()).toHaveLength(0) }) }) describe('Schema Management', () => { test('should switch schema', () => { const client = createClient(URL, KEY) const schemaClient = client.schema('personal') expect(schemaClient).toBeDefined() expect(schemaClient).toBeInstanceOf(PostgrestClient) }) }) describe('Table/View Queries', () => { test('should query table with string relation', () => { const client = createClient(URL, KEY) const queryBuilder = client.from('users') expect(queryBuilder).toBeDefined() }) }) describe('RPC Calls', () => { test('should make RPC call with arguments', () => { const client = createClient(URL, KEY) const rpcCall = client.rpc('get_status', { name_param: 'test' }) expect(rpcCall).toBeDefined() }) test('should make RPC call with options', () => { const client = createClient(URL, KEY) const rpcCall = client.rpc('get_status', { name_param: 'test' }, { head: true }) expect(rpcCall).toBeDefined() }) }) describe('Token Management', () => { describe('Token Resolution', () => { test('should resolve token from session', async () => { const expectedToken = 'test-jwt-token' const client = createClient(URL, KEY) client.auth.getSession = jest.fn().mockResolvedValue({ data: { session: { access_token: expectedToken } }, }) // @ts-ignore - accessing private method const token = await client._getAccessToken() expect(token).toBe(expectedToken) }) test('should use custom accessToken callback', async () => { const customToken = 'custom-access-token' const customAccessTokenFn = jest.fn().mockResolvedValue(customToken) const client = createClient(URL, KEY, { accessToken: customAccessTokenFn }) // @ts-ignore - accessing private method const token = await client._getAccessToken() expect(token).toBe(customToken) expect(customAccessTokenFn).toHaveBeenCalled() }) test('should fallback to supabaseKey when no session available', async () => { const client = createClient(URL, KEY) client.auth.getSession = jest.fn().mockResolvedValue({ data: { session: null }, }) // @ts-ignore - accessing private method const token = await client._getAccessToken() expect(token).toBe(KEY) }) }) describe('Realtime Authentication', () => { test('should provide access token to realtime client', async () => { const expectedToken = 'test-jwt-token' const client = createClient(URL, KEY) client.auth.getSession = jest.fn().mockResolvedValue({ data: { session: { access_token: expectedToken } }, }) const realtimeToken = await client.realtime.accessToken!() expect(realtimeToken).toBe(expectedToken) }) test('should handle authentication state changes', async () => { const client = createClient(URL, KEY) const setAuthSpy = jest.spyOn(client.realtime, 'setAuth') // @ts-ignore - accessing private method for testing client._handleTokenChanged('TOKEN_REFRESHED', 'CLIENT', 'new-token') expect(setAuthSpy).toHaveBeenCalledWith('new-token') setAuthSpy.mockClear() // @ts-ignore - accessing private method for testing client._handleTokenChanged('SIGNED_IN', 'CLIENT', 'signin-token') expect(setAuthSpy).toHaveBeenCalledWith('signin-token') setAuthSpy.mockClear() // @ts-ignore - accessing private method for testing client._handleTokenChanged('SIGNED_OUT', 'CLIENT') expect(setAuthSpy).toHaveBeenCalledWith() }) test('should update token in realtime client when setAuth is called', async () => { const client = createClient(URL, KEY) const testToken = 'test-realtime-token' client.realtime.setAuth = jest.fn(async (token) => { if (token) { ;(client.realtime as any).accessTokenValue = token } else { const freshToken = await client.realtime.accessToken!() ;(client.realtime as any).accessTokenValue = freshToken } }) await client.realtime.setAuth(testToken) expect(client.realtime.setAuth).toHaveBeenCalledWith(testToken) expect((client.realtime as any).accessTokenValue).toBe(testToken) }) }) describe('FetchWithAuth Token Integration', () => { test('should pass correct token to fetchWithAuth wrapper', async () => { const expectedToken = 'test-fetch-token' const mockFetch = jest.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({}), }) const client = createClient(URL, KEY, { global: { fetch: mockFetch }, }) client.auth.getSession = jest.fn().mockResolvedValue({ data: { session: { access_token: expectedToken } }, }) await client.from('test').select('*') expect(mockFetch).toHaveBeenCalled() const [, options] = mockFetch.mock.calls[0] expect(options.headers.get('Authorization')).toBe(`Bearer ${expectedToken}`) expect(options.headers.get('apikey')).toBe(KEY) }) test('should work across all fetchWithAuth services', async () => { const expectedToken = 'test-multi-service-token' const mockFetch = jest .fn() .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({}) }) // rest .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ data: [] }) }) // storage .mockResolvedValueOnce({ ok: true, text: () => Promise.resolve('{}'), headers: new Map(), }) // functions const client = createClient(URL, KEY, { global: { fetch: mockFetch }, }) client.auth.getSession = jest.fn().mockResolvedValue({ data: { session: { access_token: expectedToken } }, }) await client.from('test').select('*') await client.storage.from('test').list() await client.functions.invoke('test-function') expect(mockFetch).toHaveBeenCalledTimes(3) mockFetch.mock.calls.forEach(([, options]) => { expect(options.headers.get('Authorization')).toBe(`Bearer ${expectedToken}`) }) }) test('should use supabaseKey fallback in fetchWithAuth', async () => { const mockFetch = jest.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({}), }) const client = createClient(URL, KEY, { global: { fetch: mockFetch }, }) client.auth.getSession = jest.fn().mockResolvedValue({ data: { session: null }, }) await client.from('test').select('*') expect(mockFetch).toHaveBeenCalled() const [, options] = mockFetch.mock.calls[0] expect(options.headers.get('Authorization')).toBe(`Bearer ${KEY}`) expect(options.headers.get('apikey')).toBe(KEY) }) }) }) })