diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa5399d12..0fa73d9b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,6 +156,37 @@ jobs: - name: "Test Swift Package" run: swift test --skip IntegrationTests + integration-tests: + name: Integration Tests (Linux) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: "Cache Swift Package" + uses: actions/cache@v5 + with: + path: .build + key: ${{ runner.os }}-spm-integration-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm-integration- + - name: "Setup Supabase CLI" + uses: supabase/setup-cli@v1 + with: + version: latest + - name: "Start Supabase" + working-directory: Tests/IntegrationTests + run: supabase start + - name: "Reset Database" + working-directory: Tests/IntegrationTests + run: supabase db reset + - name: "Run Integration Tests" + env: + INTEGRATION_TESTS: 1 + run: swift test --filter IntegrationTests + - name: "Stop Supabase" + working-directory: Tests/IntegrationTests + if: always() + run: supabase stop + # android: # name: Android # runs-on: ubuntu-latest diff --git a/Tests/IntegrationTests/Postgrest/PostgrestBasicTests.swift b/Tests/IntegrationTests/Postgrest/PostgrestBasicTests.swift index 9912b4d1c..9b0e001c6 100644 --- a/Tests/IntegrationTests/Postgrest/PostgrestBasicTests.swift +++ b/Tests/IntegrationTests/Postgrest/PostgrestBasicTests.swift @@ -21,7 +21,7 @@ final class PostgrestBasicTests: XCTestCase { ) func testBasicSelectTable() async throws { - let response = try await client.from("users").select().execute().value as AnyJSON + let response = try await client.from("users").select("age_range,catchphrase,data,status,username").execute().value as AnyJSON assertInlineSnapshot(of: response, as: .json) { """ [ diff --git a/Tests/IntegrationTests/PostgrestIntegrationTests.swift b/Tests/IntegrationTests/PostgrestIntegrationTests.swift index 6336fcfcf..6a4428eff 100644 --- a/Tests/IntegrationTests/PostgrestIntegrationTests.swift +++ b/Tests/IntegrationTests/PostgrestIntegrationTests.swift @@ -51,10 +51,11 @@ final class IntegrationTests: XCTestCase { "INTEGRATION_TESTS not defined." ) - // Run fresh test by deleting all data. Delete without a where clause isn't supported, so have - // to do this `neq` trick to delete all data. + // Run fresh test by deleting test data. Delete without a where clause isn't supported, so have + // to do this `neq` trick to delete all data. For users, only delete rows with email (test data), + // leaving seed data with username intact. try await client.from("todos").delete().neq("id", value: UUID().uuidString).execute() - try await client.from("users").delete().neq("id", value: UUID().uuidString).execute() + try await client.from("users").delete().not("email", operator: .is, value: AnyJSON.null).execute() } func testIntegration() async throws { diff --git a/Tests/IntegrationTests/supabase/.temp/cli-latest b/Tests/IntegrationTests/supabase/.temp/cli-latest index f47ab0840..8c68db770 100644 --- a/Tests/IntegrationTests/supabase/.temp/cli-latest +++ b/Tests/IntegrationTests/supabase/.temp/cli-latest @@ -1 +1 @@ -v2.22.12 \ No newline at end of file +v2.67.1 \ No newline at end of file diff --git a/Tests/IntegrationTests/supabase/migrations/20240101000000_initial_schema.sql b/Tests/IntegrationTests/supabase/migrations/20240101000000_initial_schema.sql new file mode 100644 index 000000000..c7fb88890 --- /dev/null +++ b/Tests/IntegrationTests/supabase/migrations/20240101000000_initial_schema.sql @@ -0,0 +1,106 @@ +-- Create custom types +CREATE TYPE user_status AS ENUM ('ONLINE', 'OFFLINE'); + +-- Users table (supports both PostgrestIntegrationTests and PostgrestBasicTests) +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email TEXT, + username TEXT UNIQUE, + age_range int4range, + catchphrase TEXT, + data JSONB, + status user_status +); + +-- Todos table +CREATE TABLE todos ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + description TEXT NOT NULL, + is_complete BOOLEAN NOT NULL DEFAULT false, + tags TEXT[] DEFAULT '{}', + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Channels table +CREATE TABLE channels ( + id SERIAL PRIMARY KEY, + slug TEXT NOT NULL +); + +-- Messages table +CREATE TABLE messages ( + id SERIAL PRIMARY KEY, + channel_id INTEGER REFERENCES channels(id), + data JSONB, + message TEXT, + username TEXT REFERENCES users(username) +); + +-- Key-value storage table for Realtime tests +CREATE TABLE key_value_storage ( + key TEXT PRIMARY KEY, + value JSONB +); + +-- Create updatable view (for PostgrestBasicTests) +CREATE VIEW updatable_view AS +SELECT username, 1 AS non_updatable_column +FROM users +WHERE username IS NOT NULL; + +-- RPC function to get status +CREATE OR REPLACE FUNCTION get_status(name_param TEXT) +RETURNS TEXT AS $$ +BEGIN + RETURN (SELECT status::TEXT FROM users WHERE username = name_param); +END; +$$ LANGUAGE plpgsql; + +-- RPC function that returns void +CREATE OR REPLACE FUNCTION void_func() +RETURNS VOID AS $$ +BEGIN + -- Does nothing +END; +$$ LANGUAGE plpgsql; + +-- RPC function to delete current user (for AuthClientIntegrationTests) +CREATE OR REPLACE FUNCTION delete_user() +RETURNS VOID AS $$ +BEGIN + DELETE FROM auth.users WHERE id = auth.uid(); +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- RPC function to get username and status (for PostgrestFilterTests) +CREATE OR REPLACE FUNCTION get_username_and_status(name_param TEXT) +RETURNS TABLE(username TEXT, status user_status) AS $$ +BEGIN + RETURN QUERY SELECT u.username, u.status FROM users u WHERE u.username = name_param; +END; +$$ LANGUAGE plpgsql; + +-- RPC function to get array element (for PostgrestTransformsTests) +CREATE OR REPLACE FUNCTION get_array_element(arr TEXT[], index INT) +RETURNS TEXT AS $$ +BEGIN + RETURN arr[index]; +END; +$$ LANGUAGE plpgsql; + +-- Enable Row Level Security +ALTER TABLE users ENABLE ROW LEVEL SECURITY; +ALTER TABLE todos ENABLE ROW LEVEL SECURITY; +ALTER TABLE messages ENABLE ROW LEVEL SECURITY; +ALTER TABLE channels ENABLE ROW LEVEL SECURITY; +ALTER TABLE key_value_storage ENABLE ROW LEVEL SECURITY; + +-- Create permissive policies for testing (allow all operations) +CREATE POLICY "Allow all operations on users" ON users FOR ALL USING (true) WITH CHECK (true); +CREATE POLICY "Allow all operations on todos" ON todos FOR ALL USING (true) WITH CHECK (true); +CREATE POLICY "Allow all operations on messages" ON messages FOR ALL USING (true) WITH CHECK (true); +CREATE POLICY "Allow all operations on channels" ON channels FOR ALL USING (true) WITH CHECK (true); +CREATE POLICY "Allow all operations on key_value_storage" ON key_value_storage FOR ALL USING (true) WITH CHECK (true); + +-- Enable realtime for key_value_storage table +ALTER PUBLICATION supabase_realtime ADD TABLE key_value_storage; diff --git a/Tests/IntegrationTests/supabase/seed.sql b/Tests/IntegrationTests/supabase/seed.sql new file mode 100644 index 000000000..0edf4f71d --- /dev/null +++ b/Tests/IntegrationTests/supabase/seed.sql @@ -0,0 +1,20 @@ +-- Seed data for users table (PostgrestBasicTests) +INSERT INTO users (username, age_range, catchphrase, data, status) VALUES + ('supabot', '[1,2)', '''cat'' ''fat''', NULL, 'ONLINE'), + ('kiwicopple', '[25,35)', '''bat'' ''cat''', NULL, 'OFFLINE'), + ('awailas', '[25,35)', '''bat'' ''rat''', NULL, 'ONLINE'), + ('dragarcia', '[20,30)', '''fat'' ''rat''', NULL, 'ONLINE'); + +-- Seed data for channels table +INSERT INTO channels (id, slug) VALUES + (1, 'public'), + (2, 'random'); + +-- Seed data for messages table +INSERT INTO messages (id, channel_id, data, message, username) VALUES + (1, 1, NULL, 'Hello World 👋', 'supabot'), + (2, 2, NULL, 'Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.', 'supabot'); + +-- Reset sequences to continue from seed data +SELECT setval('channels_id_seq', (SELECT MAX(id) FROM channels)); +SELECT setval('messages_id_seq', (SELECT MAX(id) FROM messages));