-- Copyright (c) Microsoft Corporation.
-- Licensed under the PostgreSQL License.

-- Merged from: 28_bgw_lifecycle, 25_extension_creation_security
-- Tests: BGW lifecycle (pre-extension wait, post-CREATE, post-DROP, post-recreate),
--        extension creation security (non-superuser blocked, pre-existing df schema blocked,
--        ungranted role blocked, grant_usage/revoke_usage restricted)
-- Runs as postgres throughout (requires superuser for DROP/CREATE EXTENSION)
-- Note: 28 runs before 25 so the extension is in a known state when 25 starts

-- === Test: 28_bgw_lifecycle ===

-- Ensure a clean starting point
SELECT public._e2e_drop_extension_safe();

-- 1) Verify BGW does not create the duroxide provider schema pre-extension.
-- Fresh installs use the '_duroxide' schema (created by CREATE EXTENSION).
DO $$
DECLARE
    exists_before BOOLEAN;
    exists_after BOOLEAN;
BEGIN
    SELECT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = '_duroxide') INTO exists_before;
    IF exists_before THEN
        RAISE EXCEPTION 'TEST FAILED: _duroxide schema exists before CREATE EXTENSION';
    END IF;

    PERFORM pg_sleep(6);

    SELECT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = '_duroxide') INTO exists_after;
    IF exists_after THEN
        RAISE EXCEPTION 'TEST FAILED: _duroxide schema was created before CREATE EXTENSION';
    END IF;

    RAISE NOTICE 'PASSED: BGW did not create _duroxide schema pre-extension';
END $$;

-- 2) Create extension and run a simple workflow
CREATE EXTENSION pg_durable;

-- Re-grant df privileges to the E2E user
SELECT df.grant_usage('df_e2e_user');

-- Wait for the background worker to initialize
SELECT public._e2e_wait_for_worker_ready();

CREATE TEMP TABLE _test_state (instance_id TEXT);
INSERT INTO _test_state
SELECT df.start('SELECT 42 as answer', 'test-bgw-lifecycle-1');

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
    result TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_state;

    SELECT df.await_instance(inst_id) INTO status;

    IF status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED: expected completed, got %', status;
    END IF;

    SELECT r INTO result FROM df.result(inst_id) r;
    IF result NOT LIKE '%42%' THEN
        RAISE EXCEPTION 'TEST FAILED: result should contain 42, got %', result;
    END IF;

    RAISE NOTICE 'PASSED: workflow completed after CREATE EXTENSION';
END $$;

DROP TABLE _test_state;

-- 3) Drop extension and verify schema removed
SELECT public._e2e_drop_extension_safe();

DO $$
DECLARE
    schema_exists BOOLEAN;
BEGIN
    SELECT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = '_duroxide') INTO schema_exists;
    IF schema_exists THEN
        RAISE EXCEPTION 'TEST FAILED: _duroxide schema still exists after DROP EXTENSION';
    END IF;

    RAISE NOTICE 'PASSED: DROP EXTENSION removed _duroxide schema';
END $$;

-- 4) Re-create extension and run another workflow
CREATE EXTENSION pg_durable;

SELECT df.grant_usage('df_e2e_user');

SELECT public._e2e_wait_for_worker_ready();

CREATE TEMP TABLE _test_state2 (instance_id TEXT);
INSERT INTO _test_state2
SELECT df.start('SELECT 43 as answer', 'test-bgw-lifecycle-2');

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
    result TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_state2;

    SELECT df.await_instance(inst_id) INTO status;

    IF status != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED: expected completed after recreate, got %', status;
    END IF;

    SELECT r INTO result FROM df.result(inst_id) r;
    IF result NOT LIKE '%43%' THEN
        RAISE EXCEPTION 'TEST FAILED: result should contain 43, got %', result;
    END IF;

    RAISE NOTICE 'PASSED: workflow completed after re-create';
END $$;

DROP TABLE _test_state2;

-- === Test: 25_extension_creation_security ===

-- Drop and recreate the extension to test installation security
SELECT public._e2e_drop_extension_safe();

-- Test 1: Non-superuser cannot create extension
DROP USER IF EXISTS test_nonsuperuser;
CREATE USER test_nonsuperuser;

SET ROLE test_nonsuperuser;
DO $$
BEGIN
    EXECUTE 'CREATE EXTENSION pg_durable';
    RAISE EXCEPTION 'SECURITY FAILURE: Non-superuser was able to create pg_durable extension!';
EXCEPTION
    WHEN insufficient_privilege THEN
        RAISE NOTICE 'TEST 1 PASSED: Non-superuser correctly denied extension creation';
    WHEN OTHERS THEN
        IF SQLERRM ILIKE '%permission%' OR SQLERRM ILIKE '%superuser%' THEN
            RAISE NOTICE 'TEST 1 PASSED: Non-superuser correctly denied extension creation (%)', SQLERRM;
        ELSE
            RAISE EXCEPTION 'TEST 1 FAILED: Unexpected error: %', SQLERRM;
        END IF;
END $$;
RESET ROLE;

DROP USER test_nonsuperuser;

-- Test 2: Extension creation fails if 'df' schema pre-exists
CREATE SCHEMA IF NOT EXISTS df;

DO $$
DECLARE
    extension_created BOOLEAN := FALSE;
BEGIN
    BEGIN
        CREATE EXTENSION pg_durable;
        extension_created := TRUE;
    EXCEPTION
        WHEN duplicate_schema THEN
            RAISE NOTICE 'TEST 2 PASSED: Extension creation correctly prevented with pre-existing df schema';
        WHEN OTHERS THEN
            IF SQLERRM ILIKE '%schema%' OR SQLERRM ILIKE '%already exists%' OR SQLERRM ILIKE '%df%' THEN
                RAISE NOTICE 'TEST 2 PASSED: Extension creation correctly prevented with pre-existing df schema (%)', SQLERRM;
            ELSE
                RAISE EXCEPTION 'TEST 2 FAILED: Unexpected error during extension creation: %', SQLERRM;
            END IF;
    END;
    
    IF extension_created THEN
        RAISE EXCEPTION 'SECURITY FAILURE: Extension created successfully even with pre-existing df schema!';
    END IF;
END $$;

DROP SCHEMA IF EXISTS df CASCADE;

-- Recreate the extension properly for remaining tests
CREATE EXTENSION pg_durable;

-- Test 3: Ungranted role cannot call df.sql() / df.start()
-- (At this point extension exists but df_e2e_user has no explicit grants yet)
SET ROLE df_e2e_user;

DO $$
BEGIN
    BEGIN
        PERFORM df.sql('SELECT 1');
        RAISE EXCEPTION 'SECURITY FAILURE: ungranted role was able to call df.sql()';
    EXCEPTION
        WHEN insufficient_privilege THEN
            RAISE NOTICE 'TEST 3a PASSED: df.sql() blocked for ungranted role (insufficient privilege)';
    END;

    BEGIN
        PERFORM df.start(df.sql('SELECT 1'), 'ungranted-role-test');
        RAISE EXCEPTION 'SECURITY FAILURE: ungranted role was able to call df.start()';
    EXCEPTION
        WHEN insufficient_privilege THEN
            RAISE NOTICE 'TEST 3b PASSED: df.start() blocked for ungranted role (insufficient privilege)';
    END;
END $$;

RESET ROLE;

-- Test 3c: Admin helpers are not executable by non-superusers by default
DO $$
DECLARE
    can_grant_usage BOOLEAN;
    can_revoke_usage BOOLEAN;
BEGIN
    SELECT has_function_privilege(
        'df_e2e_user',
        'df.grant_usage(text, boolean, boolean)',
        'EXECUTE'
    ) INTO can_grant_usage;

    SELECT has_function_privilege(
        'df_e2e_user',
        'df.revoke_usage(text)',
        'EXECUTE'
    ) INTO can_revoke_usage;

    IF can_grant_usage THEN
        RAISE EXCEPTION 'SECURITY FAILURE: df.grant_usage() is executable by df_e2e_user before any explicit helper grant';
    END IF;

    IF can_revoke_usage THEN
        RAISE EXCEPTION 'SECURITY FAILURE: df.revoke_usage() is executable by df_e2e_user before any explicit helper grant';
    END IF;

    RAISE NOTICE 'TEST 3c PASSED: admin helpers are not executable by non-superusers by default';
END $$;

-- Re-grant df privileges to the E2E user
SELECT df.grant_usage('df_e2e_user');

-- Test 4: df.grant_usage() does not grant EXECUTE on admin helpers to the target role
DO $$
DECLARE
    can_grant_usage BOOLEAN;
    can_revoke_usage BOOLEAN;
BEGIN
    SELECT has_function_privilege(
        'df_e2e_user',
        'df.grant_usage(text, boolean, boolean)',
        'EXECUTE'
    ) INTO can_grant_usage;

    SELECT has_function_privilege(
        'df_e2e_user',
        'df.revoke_usage(text)',
        'EXECUTE'
    ) INTO can_revoke_usage;

    IF can_grant_usage THEN
        RAISE EXCEPTION 'SECURITY FAILURE: df.grant_usage() granted EXECUTE on itself to df_e2e_user';
    END IF;

    IF can_revoke_usage THEN
        RAISE EXCEPTION 'SECURITY FAILURE: df.grant_usage() granted EXECUTE on df.revoke_usage() to df_e2e_user';
    END IF;

    RAISE NOTICE 'TEST 4 PASSED: df.grant_usage() does not grant EXECUTE on admin helpers';
END $$;

-- Test 5: If EXECUTE is granted manually but without WITH GRANT OPTION on
-- underlying objects, the inner GRANT/REVOKE statements fail (PostgreSQL's
-- native privilege checks prevent escalation).
DROP ROLE IF EXISTS test_helper_grantee;
CREATE ROLE test_helper_grantee LOGIN;

GRANT USAGE ON SCHEMA df TO test_helper_grantee;
GRANT EXECUTE ON FUNCTION df.grant_usage(text, boolean, boolean) TO test_helper_grantee;
GRANT EXECUTE ON FUNCTION df.revoke_usage(text) TO test_helper_grantee;

SET ROLE test_helper_grantee;

DO $$
BEGIN
    BEGIN
        PERFORM df.grant_usage('df_e2e_user');
        RAISE EXCEPTION 'SECURITY FAILURE: manually granted non-superuser was able to call df.grant_usage()';
    EXCEPTION
        WHEN insufficient_privilege THEN
            RAISE NOTICE 'TEST 5a PASSED: df.grant_usage() blocked by PostgreSQL privilege check';
        WHEN OTHERS THEN
            IF SQLERRM ILIKE '%permission denied%' THEN
                RAISE NOTICE 'TEST 5a PASSED: df.grant_usage() blocked by PostgreSQL privilege check (%)', SQLERRM;
            ELSE
                RAISE EXCEPTION 'TEST 5a UNEXPECTED ERROR: %', SQLERRM;
            END IF;
    END;

    BEGIN
        PERFORM df.revoke_usage('df_e2e_user');
        RAISE EXCEPTION 'SECURITY FAILURE: manually granted non-superuser was able to call df.revoke_usage()';
    EXCEPTION
        WHEN insufficient_privilege THEN
            RAISE NOTICE 'TEST 5b PASSED: df.revoke_usage() blocked by PostgreSQL privilege check';
        WHEN OTHERS THEN
            IF SQLERRM ILIKE '%permission denied%' OR SQLERRM ILIKE '%cannot revoke df privileges%' THEN
                RAISE NOTICE 'TEST 5b PASSED: df.revoke_usage() blocked (%)', SQLERRM;
            ELSE
                RAISE EXCEPTION 'TEST 5b UNEXPECTED ERROR: %', SQLERRM;
            END IF;
    END;
END $$;

RESET ROLE;

REVOKE EXECUTE ON FUNCTION df.grant_usage(text, boolean, boolean) FROM test_helper_grantee;
REVOKE EXECUTE ON FUNCTION df.revoke_usage(text) FROM test_helper_grantee;
REVOKE USAGE ON SCHEMA df FROM test_helper_grantee;
DROP ROLE test_helper_grantee;

-- Wait for the background worker to fully reinitialize after the drop/recreate.
SELECT public._e2e_wait_for_worker_ready();

-- Verify the worker is actually processing functions
CREATE TEMP TABLE _test_state_25 (instance_id TEXT);
INSERT INTO _test_state_25 SELECT df.start(df.sql('SELECT 1'), 'verify-worker-ready');

DO $$
DECLARE
    inst_id TEXT;
    final_status TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_state_25;
    SELECT df.await_instance(inst_id, 30) INTO final_status;

    IF final_status != 'completed' THEN
        RAISE EXCEPTION 'TEST SETUP FAILED: worker did not recover after extension recreate (status=%)', final_status;
    END IF;

    RAISE NOTICE 'Background worker reinitialized successfully';
END $$;

DROP TABLE _test_state_25;

-- Verify extension is properly installed
DO $$
DECLARE
    schema_exists BOOLEAN;
    extension_exists BOOLEAN;
BEGIN
    SELECT EXISTS(
        SELECT 1 FROM pg_namespace WHERE nspname = 'df'
    ) INTO schema_exists;
    
    SELECT EXISTS(
        SELECT 1 FROM pg_extension WHERE extname = 'pg_durable'
    ) INTO extension_exists;
    
    IF NOT schema_exists THEN
        RAISE EXCEPTION 'TEST SETUP FAILED: df schema not created after extension installation';
    END IF;
    
    IF NOT extension_exists THEN
        RAISE EXCEPTION 'TEST SETUP FAILED: pg_durable extension not installed';
    END IF;
    
    RAISE NOTICE 'Extension restored successfully';
END $$;

SELECT 'TEST PASSED' AS result;
