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

-- Merged from: 09_monitoring, 10_explain, 31_explain_plain_sql
-- Tests: list_instances, instance_info, status, result, df.explain() on live and dry-run,
--        df.explain() on plain SQL auto-wrap
SET SESSION AUTHORIZATION df_e2e_user;

-- === Test: 09_monitoring ===

CREATE TEMP TABLE _test_state (instance_id TEXT);

INSERT INTO _test_state SELECT df.start('SELECT 123', 'test-monitoring-label');

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
    found BOOLEAN;
    info_status TEXT;
    result TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_state;
    RAISE NOTICE 'Testing instance: %', inst_id;

    SELECT df.await_instance(inst_id) INTO status;
    
    -- Test list_instances
    SELECT EXISTS (
        SELECT 1 FROM df.list_instances() 
        WHERE list_instances.instance_id = inst_id
    ) INTO found;
    
    IF NOT found THEN
        RAISE EXCEPTION 'TEST FAILED: instance not found in list_instances()';
    END IF;
    
    -- Test instance_info
    SELECT i.status INTO info_status FROM df.instance_info(inst_id) i;
    IF info_status IS NULL THEN
        RAISE EXCEPTION 'TEST FAILED: instance_info returned NULL status';
    END IF;
    
    -- Test status
    IF lower(status) != 'completed' THEN
        RAISE EXCEPTION 'TEST FAILED: expected completed, got %', status;
    END IF;
    
    -- Test result
    SELECT r INTO result FROM df.result(inst_id) r;
    IF result NOT LIKE '%123%' THEN
        RAISE EXCEPTION 'TEST FAILED: result should contain 123, got %', result;
    END IF;
    
    RAISE NOTICE 'TEST PASSED: monitoring';
END $$;

DROP TABLE _test_state;

-- === Test: 10_explain ===

-- Test dry-run explain (use $body$ to avoid conflict with inner $$)
DO $body$
DECLARE
    explain_output TEXT;
BEGIN
    SELECT df.explain($$ 'SELECT 1' ~> 'SELECT 2' $$) INTO explain_output;
    
    IF explain_output IS NULL OR explain_output = '' THEN
        RAISE EXCEPTION 'TEST FAILED: explain returned empty output';
    END IF;
    
    IF explain_output NOT LIKE '%SQL%' THEN
        RAISE EXCEPTION 'TEST FAILED: explain should contain SQL nodes, got: %', explain_output;
    END IF;
    
    RAISE NOTICE 'Dry-run explain passed';
END $body$;

-- Test dry-run explain renders RACE branches for both operator and function forms
DO $body$
DECLARE
    explain_output TEXT;
BEGIN
    SELECT df.explain($$ 'SELECT ''winner''' | df.sleep(30) $$) INTO explain_output;

    IF explain_output NOT LIKE '%RACE%'
        OR explain_output NOT LIKE '%branch 1:%'
        OR explain_output NOT LIKE '%branch 2:%'
        OR explain_output NOT LIKE '%SELECT ''winner''%'
        OR explain_output NOT LIKE '%SLEEP 30s%' THEN
        RAISE EXCEPTION 'TEST FAILED: operator RACE explain should show both branches, got: %', explain_output;
    END IF;

    SELECT df.explain($$ df.race('SELECT ''winner''', df.sleep(30)) $$) INTO explain_output;

    IF explain_output NOT LIKE '%RACE%'
        OR explain_output NOT LIKE '%branch 1:%'
        OR explain_output NOT LIKE '%branch 2:%'
        OR explain_output NOT LIKE '%SELECT ''winner''%'
        OR explain_output NOT LIKE '%SLEEP 30s%' THEN
        RAISE EXCEPTION 'TEST FAILED: df.race() explain should show both branches, got: %', explain_output;
    END IF;

    RAISE NOTICE 'Dry-run RACE explain passed';
END $body$;

-- Test live instance explain
CREATE TEMP TABLE _test_state (instance_id TEXT);

INSERT INTO _test_state SELECT df.start('SELECT 1' ~> 'SELECT 2', 'test-explain');

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
    explain_output TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_state;
    RAISE NOTICE 'Testing instance: %', inst_id;

    SELECT df.await_instance(inst_id) INTO status;
    
    SELECT df.explain(inst_id) INTO explain_output;
    
    IF explain_output IS NULL OR explain_output = '' THEN
        RAISE EXCEPTION 'TEST FAILED: explain returned empty output for live instance';
    END IF;
    
    IF explain_output NOT LIKE '%ompleted%' AND explain_output NOT LIKE '%✓%' THEN
        RAISE EXCEPTION 'TEST FAILED: explain should show completion status, got: %', explain_output;
    END IF;
    
    RAISE NOTICE 'TEST PASSED: explain';
END $$;

DROP TABLE _test_state;

-- Test live RACE explain shows the skipped losing branch
CREATE TEMP TABLE _test_race_explain_state (instance_id TEXT);

INSERT INTO _test_race_explain_state
SELECT df.start(df.race('SELECT ''winner''', df.sleep(10)), 'test-race-explain');

DO $$
DECLARE
    inst_id TEXT;
    status TEXT;
    explain_output TEXT;
BEGIN
    SELECT instance_id INTO inst_id FROM _test_race_explain_state;

    SELECT df.await_instance(inst_id, 20) INTO status;

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

    SELECT df.explain(inst_id) INTO explain_output;

    IF explain_output NOT LIKE '%RACE%'
        OR explain_output NOT LIKE '%branch 1:%'
        OR explain_output NOT LIKE '%branch 2:%'
        OR explain_output NOT LIKE '%SELECT ''winner''%'
        OR explain_output NOT LIKE '%SLEEP 10s%' THEN
        RAISE EXCEPTION 'TEST FAILED: live RACE explain should show both branches, got: %', explain_output;
    END IF;

    IF explain_output NOT LIKE '%⊘%' THEN
        RAISE EXCEPTION 'TEST FAILED: live RACE explain should show skipped marker for losing branch, got: %', explain_output;
    END IF;

    RAISE NOTICE 'TEST PASSED: live race explain';
END $$;

DROP TABLE _test_race_explain_state;

-- === Test: 31_explain_plain_sql ===

DO $body$
DECLARE
    explain_output TEXT;
BEGIN
    SELECT df.explain('SELECT 1') INTO explain_output;

    IF explain_output IS NULL OR explain_output = '' THEN
        RAISE EXCEPTION 'TEST FAILED: explain returned empty output';
    END IF;

    IF explain_output NOT LIKE '%SQL:%' OR explain_output NOT LIKE '%SELECT 1%' THEN
        RAISE EXCEPTION 'TEST FAILED: explain should show SQL: SELECT 1, got: %', explain_output;
    END IF;

    RAISE NOTICE 'TEST PASSED: explain plain SQL';
END $body$;

-- === Test: multi-instance list ordering + list/instance_info equivalence ===
-- Exercises the batched instance-info reassembly in df.list_instances(): start
-- several same-user instances with distinct outputs, then assert (a) they appear
-- newest-first (created_at DESC) in list_instances(), and (b) function_name,
-- execution_count, and output for each agree with df.instance_info() (the
-- per-instance path), proving the batch lookup reassembles the right metadata
-- against the right id.

CREATE TEMP TABLE _multi_state (n INT, instance_id TEXT);

-- Start three instances in separate statements (separate transactions) with a
-- short gap so created_at (DEFAULT now(), the transaction timestamp) is strictly
-- increasing and the created_at DESC order is deterministic.
INSERT INTO _multi_state SELECT 1, df.start('SELECT 1001', 'sf3-a');
SELECT pg_sleep(0.05);
INSERT INTO _multi_state SELECT 2, df.start('SELECT 1002', 'sf3-b');
SELECT pg_sleep(0.05);
INSERT INTO _multi_state SELECT 3, df.start('SELECT 1003', 'sf3-c');

DO $multi$
DECLARE
    ids TEXT[];
    expected_order TEXT[];
    listed_order TEXT[];
    rec RECORD;
    li RECORD;
    ii RECORD;
    settled INT;
    attempts INT := 0;
BEGIN
    -- Await all three to completion.
    FOR rec IN SELECT instance_id FROM _multi_state LOOP
        PERFORM df.await_instance(rec.instance_id);
    END LOOP;

    SELECT array_agg(instance_id ORDER BY n) INTO ids FROM _multi_state;
    -- created_at DESC => most recently started first => reverse of start order.
    SELECT array_agg(instance_id ORDER BY n DESC) INTO expected_order FROM _multi_state;

    -- await_instance() returns when df.instances.status is terminal, but
    -- duroxide's execution output can become visible a moment later. Both
    -- monitoring paths (list_instances and instance_info) observe the same
    -- eventual state, so wait until output has materialized for all three before
    -- comparing snapshots, otherwise we would race the completion boundary.
    LOOP
        SELECT count(*) INTO settled
        FROM df.list_instances()
        WHERE list_instances.instance_id = ANY(ids) AND output IS NOT NULL;
        EXIT WHEN settled = 3 OR attempts > 100;
        PERFORM pg_sleep(0.1);
        attempts := attempts + 1;
    END LOOP;

    IF settled <> 3 THEN
        RAISE EXCEPTION 'TEST FAILED: only % of 3 instances have materialized output in list_instances()', settled;
    END IF;

    -- (a) Order of our three ids within list_instances() output. WITH ORDINALITY
    -- numbers rows in the exact order the function emits them (the just-created
    -- instances are newest, so they sort first under created_at DESC).
    SELECT array_agg(instance_id ORDER BY ord) INTO listed_order
    FROM df.list_instances()
        WITH ORDINALITY AS t(instance_id, label, function_name, status, execution_count, output, ord)
    WHERE t.instance_id = ANY(ids);

    IF listed_order IS DISTINCT FROM expected_order THEN
        RAISE EXCEPTION 'TEST FAILED: list_instances order % != expected created_at DESC order %',
            listed_order, expected_order;
    END IF;

    -- (b) Per-instance equivalence between list_instances() and instance_info().
    -- list_instances.execution_count maps to instance_info.current_execution_id.
    -- Distinct outputs (1001/1002/1003) prove the batch reassembly maps each
    -- instance's metadata back to the right id rather than scrambling rows.
    FOR li IN
        SELECT instance_id, function_name, execution_count, output
        FROM df.list_instances()
        WHERE list_instances.instance_id = ANY(ids)
    LOOP
        SELECT function_name, current_execution_id, output
        INTO ii
        FROM df.instance_info(li.instance_id);

        IF ii.function_name IS DISTINCT FROM li.function_name THEN
            RAISE EXCEPTION 'TEST FAILED: function_name mismatch for %: list=% info=%',
                li.instance_id, li.function_name, ii.function_name;
        END IF;
        IF ii.current_execution_id IS DISTINCT FROM li.execution_count THEN
            RAISE EXCEPTION 'TEST FAILED: execution_count mismatch for %: list=% info=%',
                li.instance_id, li.execution_count, ii.current_execution_id;
        END IF;
        IF ii.output IS DISTINCT FROM li.output THEN
            RAISE EXCEPTION 'TEST FAILED: output mismatch for %: list=% info=%',
                li.instance_id, li.output, ii.output;
        END IF;
    END LOOP;

    RAISE NOTICE 'TEST PASSED: multi-instance ordering + list/instance_info equivalence';
END $multi$;

DROP TABLE _multi_state;

-- === Test: PR4 — label_filter, timestamps, and keyset pagination (issues #87/#146) ===
-- Start five instances that all share one label ('pr4-page') so the label filter
-- scopes df.list_instances() to exactly this set, independent of any other
-- instances this role created earlier in the suite. That makes the keyset
-- pagination assertion self-contained and deterministic. The five are created in
-- two batches: the first three share one transaction timestamp and the last two
-- share a later one, so created_at has ties within each batch -- exercising BOTH
-- branches of the keyset predicate (created_at < cursor, and created_at = cursor
-- AND id > cursor_id).
CREATE TEMP TABLE _page_state (instance_id TEXT);
INSERT INTO _page_state SELECT df.start('SELECT ' || g, 'pr4-page') FROM generate_series(2001, 2003) g;
SELECT pg_sleep(0.05);
INSERT INTO _page_state SELECT df.start('SELECT ' || g, 'pr4-page') FROM generate_series(2004, 2005) g;

DO $page$
DECLARE
    rec RECORD;
    total INT;
    labeled INT;
    with_created INT;
    with_completed INT;
    single_page_cursors INT;
    ref_ids TEXT[];
    paged_ids TEXT[];
    page_ids TEXT[];
    page_cursor TEXT;
    page_n INT;
    cur TEXT;
    pages INT := 0;
    settled INT;
    attempts INT := 0;
    combo_status TEXT;
    combo INT;
    combo_none INT;
BEGIN
    FOR rec IN SELECT instance_id FROM _page_state LOOP
        PERFORM df.await_instance(rec.instance_id);
    END LOOP;

    -- Wait until every instance's output has materialized (same completion-boundary
    -- race the SF-3 block documents).
    LOOP
        SELECT count(*) INTO settled
        FROM df.list_instances(NULL, 100, 'pr4-page')
        WHERE output IS NOT NULL;
        EXIT WHEN settled = 5 OR attempts > 100;
        PERFORM pg_sleep(0.1);
        attempts := attempts + 1;
    END LOOP;
    IF settled <> 5 THEN
        RAISE EXCEPTION 'TEST FAILED: only % of 5 pr4-page instances settled', settled;
    END IF;

    -- (a) label_filter returns exactly our five, all carrying that label, and the
    -- timestamps sourced from df.instances are populated (created_at always;
    -- completed_at because every instance completed successfully).
    SELECT count(*),
           count(*) FILTER (WHERE label = 'pr4-page'),
           count(*) FILTER (WHERE created_at IS NOT NULL),
           count(*) FILTER (WHERE completed_at IS NOT NULL)
    INTO total, labeled, with_created, with_completed
    FROM df.list_instances(NULL, 100, 'pr4-page');

    IF total <> 5 OR labeled <> 5 THEN
        RAISE EXCEPTION 'TEST FAILED: label_filter returned % rows (% labeled), expected 5/5', total, labeled;
    END IF;
    IF with_created <> 5 THEN
        RAISE EXCEPTION 'TEST FAILED: % of 5 rows have created_at, expected 5', with_created;
    END IF;
    IF with_completed <> 5 THEN
        RAISE EXCEPTION 'TEST FAILED: % of 5 completed rows have completed_at, expected 5', with_completed;
    END IF;

    -- (a2) status_filter composes with label_filter. The label-only calls above
    -- never push a status placeholder, so this is the only assertion that
    -- exercises the two-filter dynamic placeholder numbering (status = $1 AND
    -- label = $2). Read the actual stored status of our set (uniform: all five
    -- completed) rather than hard-coding the literal, then assert the combined
    -- filter returns exactly those five and that a non-matching status returns none.
    SELECT status INTO combo_status FROM df.list_instances(NULL, 1, 'pr4-page');
    SELECT count(*) INTO combo FROM df.list_instances(combo_status, 100, 'pr4-page');
    IF combo <> 5 THEN
        RAISE EXCEPTION 'TEST FAILED: status+label filter returned % rows for status %, expected 5', combo, combo_status;
    END IF;
    SELECT count(*) INTO combo_none
    FROM df.list_instances('definitely-not-a-status', 100, 'pr4-page');
    IF combo_none <> 0 THEN
        RAISE EXCEPTION 'TEST FAILED: status+label filter with non-matching status returned % rows, expected 0', combo_none;
    END IF;

    -- (b) A single page large enough to hold the whole set must report no further
    -- page: next_cursor is NULL on every row.
    SELECT count(*) FILTER (WHERE next_cursor IS NOT NULL) INTO single_page_cursors
    FROM df.list_instances(NULL, 100, 'pr4-page');
    IF single_page_cursors <> 0 THEN
        RAISE EXCEPTION 'TEST FAILED: single full page exposed % non-null next_cursor', single_page_cursors;
    END IF;

    -- Authoritative order (created_at DESC, id ASC) from one unpaginated call.
    SELECT array_agg(instance_id ORDER BY ord) INTO ref_ids
    FROM df.list_instances(NULL, 100, 'pr4-page')
        WITH ORDINALITY AS t(instance_id, label, function_name, status, execution_count, output, created_at, completed_at, next_cursor, ord);

    -- (c) Walk the same set two-at-a-time via next_cursor and rebuild the id list.
    -- A correct keyset traversal must reproduce the unpaginated order exactly with
    -- no duplicates and no gaps.
    cur := NULL;
    paged_ids := '{}';
    LOOP
        SELECT array_agg(instance_id ORDER BY ord), max(next_cursor), count(*)
        INTO page_ids, page_cursor, page_n
        FROM df.list_instances(NULL, 2, 'pr4-page', cur)
            WITH ORDINALITY AS t(instance_id, label, function_name, status, execution_count, output, created_at, completed_at, next_cursor, ord);

        EXIT WHEN page_n = 0;
        paged_ids := paged_ids || page_ids;
        pages := pages + 1;

        -- A non-final page (cursor present) must be full and advance; the final
        -- page carries a NULL cursor and ends the walk.
        IF page_cursor IS NOT NULL AND page_n <> 2 THEN
            RAISE EXCEPTION 'TEST FAILED: non-final page had % rows, expected 2', page_n;
        END IF;
        EXIT WHEN page_cursor IS NULL;
        cur := page_cursor;
        EXIT WHEN pages > 50;
    END LOOP;

    IF paged_ids IS DISTINCT FROM ref_ids THEN
        RAISE EXCEPTION 'TEST FAILED: paginated order % != unpaginated order %', paged_ids, ref_ids;
    END IF;
    IF pages <> 3 THEN
        RAISE EXCEPTION 'TEST FAILED: 5 rows at limit 2 produced % pages, expected 3', pages;
    END IF;

    -- (d) A malformed cursor is a client error, not a silent restart.
    BEGIN
        PERFORM instance_id FROM df.list_instances(NULL, 2, 'pr4-page', 'zz');
        RAISE EXCEPTION 'TEST FAILED: invalid after_cursor did not raise';
    EXCEPTION WHEN OTHERS THEN
        IF SQLERRM LIKE '%TEST FAILED%' THEN
            RAISE;
        END IF;
        -- expected: df.list_instances: invalid after_cursor
    END;

    -- (d2) A structurally valid cursor (correct hex + recognized version) that
    -- carries a non-timestamp payload must ALSO be rejected. Without up-front
    -- validation this would fail the ::timestamptz cast deep in the query and be
    -- swallowed as an empty page -- silently ending a client's pagination instead
    -- of surfacing the bad token. The payload below decodes cleanly to
    -- ('not-a-timestamp', 'abc') under version v1.
    BEGIN
        PERFORM instance_id FROM df.list_instances(
            NULL, 2, 'pr4-page',
            encode(convert_to('v1|not-a-timestamp|abc', 'UTF8'), 'hex'));
        RAISE EXCEPTION 'TEST FAILED: malformed-timestamp after_cursor did not raise';
    EXCEPTION WHEN OTHERS THEN
        IF SQLERRM LIKE '%TEST FAILED%' THEN
            RAISE;
        END IF;
        -- expected: df.list_instances: invalid after_cursor
    END;

    RAISE NOTICE 'TEST PASSED: label_filter + timestamps + keyset pagination';
END $page$;

DROP TABLE _page_state;

-- === Test: PR4 — completed_at is NULL for a non-completed (failed) instance ===
-- df.instances.completed_at is set only on successful completion (it stays NULL
-- for failed/cancelled instances). The pr4-page block above covers the completed
-- case; this block covers the failed case so the timestamp column's contract is
-- asserted in both directions. 'SELECT 1/0' fails the SQL node -> the instance
-- ends 'failed'.
CREATE TEMP TABLE _fail_state (instance_id TEXT);
INSERT INTO _fail_state SELECT df.start('SELECT 1/0', 'pr4-fail');

DO $fail$
DECLARE
    fid TEXT;
    fstatus TEXT;
    seen INT;
    fcreated INT;
    fcompleted INT;
    attempts INT := 0;
BEGIN
    SELECT instance_id INTO fid FROM _fail_state;
    fstatus := df.await_instance(fid);
    IF fstatus <> 'failed' THEN
        RAISE EXCEPTION 'TEST FAILED: pr4-fail instance status = %, expected failed', fstatus;
    END IF;

    -- Wait until the failed instance is resolvable through list_instances (its
    -- duroxide info row is available). A failed instance has no output, so we
    -- cannot wait on output IS NOT NULL like the completed case does.
    LOOP
        SELECT count(*) INTO seen FROM df.list_instances(NULL, 100, 'pr4-fail');
        EXIT WHEN seen = 1 OR attempts > 100;
        PERFORM pg_sleep(0.1);
        attempts := attempts + 1;
    END LOOP;
    IF seen <> 1 THEN
        RAISE EXCEPTION 'TEST FAILED: pr4-fail instance not listed (seen=%)', seen;
    END IF;

    SELECT count(*) FILTER (WHERE created_at IS NOT NULL),
           count(*) FILTER (WHERE completed_at IS NOT NULL)
    INTO fcreated, fcompleted
    FROM df.list_instances(NULL, 100, 'pr4-fail');

    IF fcreated <> 1 THEN
        RAISE EXCEPTION 'TEST FAILED: failed instance created_at not populated (count=%)', fcreated;
    END IF;
    IF fcompleted <> 0 THEN
        RAISE EXCEPTION 'TEST FAILED: failed instance has completed_at set (count=%), expected NULL', fcompleted;
    END IF;

    RAISE NOTICE 'TEST PASSED: completed_at is NULL for failed instance';
END $fail$;

DROP TABLE _fail_state;

RESET SESSION AUTHORIZATION;

-- ============================================================================
-- list_instances max-limit GUC (pg_durable.list_instances_max_limit, PR5 / #146)
-- Runs as the postgres superuser (after RESET SESSION AUTHORIZATION) so it can
-- SET the Suset-context GUC. Verifies: (1) default cap raises a loud error on
-- both overloads instead of silently truncating, (2) the cap is tunable at
-- runtime, and (3) an ordinary (non-superuser) caller cannot raise it.
-- ============================================================================

-- (1) Default cap (1000): a request above the cap raises on BOTH overloads.
DO $lim_default$
DECLARE
    msg TEXT;
BEGIN
    -- Basic overload (2 args).
    BEGIN
        PERFORM * FROM df.list_instances(NULL, 1001);
        RAISE EXCEPTION 'TEST FAILED: list_instances(NULL, 1001) did not raise (expected over-limit error)';
    EXCEPTION WHEN OTHERS THEN
        msg := SQLERRM;
        IF strpos(msg, 'list_instances_max_limit') = 0 THEN
            RAISE EXCEPTION 'TEST FAILED: basic overload error did not mention the GUC: %', msg;
        END IF;
    END;

    -- Paginated overload (3 args).
    BEGIN
        PERFORM * FROM df.list_instances(NULL, 1001, NULL);
        RAISE EXCEPTION 'TEST FAILED: list_instances(NULL, 1001, NULL) did not raise (expected over-limit error)';
    EXCEPTION WHEN OTHERS THEN
        msg := SQLERRM;
        IF strpos(msg, 'list_instances_max_limit') = 0 THEN
            RAISE EXCEPTION 'TEST FAILED: paginated overload error did not mention the GUC: %', msg;
        END IF;
    END;

    -- At the default cap (1000): must NOT raise on either overload. This pins the
    -- boundary to "> cap" (not ">= cap") so the at-limit page is provably allowed.
    PERFORM * FROM df.list_instances(NULL, 1000);
    PERFORM * FROM df.list_instances(NULL, 1000, NULL);

    RAISE NOTICE 'TEST PASSED: default list_instances_max_limit raises over-limit error';
END $lim_default$;

-- (2) The cap is tunable at runtime: lower it, confirm at-limit passes and
--     over-limit raises with the new ceiling echoed in the message.
SET pg_durable.list_instances_max_limit = 3;
DO $lim_tunable$
DECLARE
    msg TEXT;
BEGIN
    -- At the limit: must NOT raise (basic and paginated overloads).
    PERFORM * FROM df.list_instances(NULL, 3);
    PERFORM * FROM df.list_instances(NULL, 3, NULL);

    -- Over the (lowered) limit: must raise and echo the configured ceiling (basic).
    BEGIN
        PERFORM * FROM df.list_instances(NULL, 4);
        RAISE EXCEPTION 'TEST FAILED: list_instances(NULL, 4) did not raise with cap = 3';
    EXCEPTION WHEN OTHERS THEN
        msg := SQLERRM;
        IF strpos(msg, 'list_instances_max_limit (3)') = 0 THEN
            RAISE EXCEPTION 'TEST FAILED: over-limit error did not echo the configured cap (3): %', msg;
        END IF;
    END;

    -- Same guard on the paginated overload: must raise and echo the same ceiling.
    BEGIN
        PERFORM * FROM df.list_instances(NULL, 4, NULL);
        RAISE EXCEPTION 'TEST FAILED: list_instances(NULL, 4, NULL) did not raise with cap = 3';
    EXCEPTION WHEN OTHERS THEN
        msg := SQLERRM;
        IF strpos(msg, 'list_instances_max_limit (3)') = 0 THEN
            RAISE EXCEPTION 'TEST FAILED: paginated over-limit error did not echo the configured cap (3): %', msg;
        END IF;
    END;

    RAISE NOTICE 'TEST PASSED: list_instances_max_limit is tunable at runtime';
END $lim_tunable$;
RESET pg_durable.list_instances_max_limit;

-- (3) Guardrail: a non-superuser cannot raise the cap (Suset context).
SET SESSION AUTHORIZATION df_e2e_user;
DO $lim_guard$
DECLARE
    allowed BOOLEAN := false;
    sqlstate_caught TEXT;
BEGIN
    BEGIN
        PERFORM set_config('pg_durable.list_instances_max_limit', '1000000', false);
        allowed := true;  -- set_config returned without error => guardrail breached
    EXCEPTION
        WHEN insufficient_privilege THEN
            NULL;  -- expected: SQLSTATE 42501, permission denied to set parameter
        WHEN OTHERS THEN
            sqlstate_caught := SQLSTATE;
            IF sqlstate_caught <> '42501' THEN
                RAISE EXCEPTION 'TEST FAILED: expected insufficient_privilege (42501) when a non-superuser raises the GUC, got SQLSTATE %: %', sqlstate_caught, SQLERRM;
            END IF;
    END;

    IF allowed THEN
        RAISE EXCEPTION 'TEST FAILED: non-superuser was allowed to raise list_instances_max_limit';
    END IF;

    RAISE NOTICE 'TEST PASSED: non-superuser cannot raise list_instances_max_limit';
END $lim_guard$;
RESET SESSION AUTHORIZATION;

SELECT 'TEST PASSED' AS result;
