\set ECHO none
\pset format unaligned
SET search_path TO provsql_test,provsql;

-- 1. The GUC is registered and defaults to the empty string.
SHOW provsql.tool_search_path;

-- 2. Stage a stub `graph-easy` in a private directory.  The stub ignores
--    its input file and writes a fixed marker to whatever path it received
--    via --output=, then exits 0.  Permissions allow the postgres backend
--    OS user (which may differ from the test user) to read and execute.
\! rm -rf /tmp/provsql_tsp_test && mkdir -p /tmp/provsql_tsp_test
\! printf '#!/bin/sh\nfor a in "$@"; do case "$a" in --output=*) printf STUB-MARKER > "${a#--output=}";; esac; done\n' > /tmp/provsql_tsp_test/graph-easy
\! chmod 755 /tmp/provsql_tsp_test /tmp/provsql_tsp_test/graph-easy

-- 3. Point the GUC at the stub directory; SHOW must reflect it.
SET provsql.tool_search_path = '/tmp/provsql_tsp_test';
SHOW provsql.tool_search_path;

-- 4. Trigger the dot renderer.  With the stub directory prepended to PATH
--    inside the backend, view_circuit's call to `graph-easy` resolves to
--    the stub regardless of whether the real graph-easy is installed, so
--    the rendered output equals the marker the stub wrote.  This is what
--    proves the GUC value is actually applied around system() calls.
CREATE TABLE tsp_result AS
SELECT view_circuit(provenance(), 'd') AS rendered
FROM (
  SELECT p1.name FROM personnel p1, personnel p2
  WHERE p1.id=1 AND p2.id=1
) t;
SELECT remove_provenance('tsp_result');
SELECT rendered FROM tsp_result;
DROP TABLE tsp_result;

-- 5. Replace the stub with one that exits 42.  format_external_tool_status
--    should decode the WEXITSTATUS into "exited with status 42" rather
--    than the old generic "Error executing graph-easy".  The DO/EXCEPTION
--    wrapper turns the substring match into a stable, sqlstate-independent
--    NOTICE so the test does not depend on PG locale or error-prefix
--    formatting.
\! printf '#!/bin/sh\nexit 42\n' > /tmp/provsql_tsp_test/graph-easy
\! chmod 755 /tmp/provsql_tsp_test/graph-easy

DO $$
BEGIN
  PERFORM view_circuit(provenance(), 'd')
  FROM (
    SELECT p1.name FROM personnel p1, personnel p2
    WHERE p1.id=1 AND p2.id=1
  ) t;
  RAISE NOTICE 'unexpected: view_circuit succeeded';
EXCEPTION WHEN OTHERS THEN
  IF SQLERRM LIKE '%exited with status 42%' THEN
    RAISE NOTICE 'graph-easy exit 42 surfaces as structured error';
  ELSE
    RAISE NOTICE 'unexpected error: %', SQLERRM;
  END IF;
END $$;

-- 6. RESET restores the empty default.
RESET provsql.tool_search_path;
SHOW provsql.tool_search_path;

-- 7. provsql.tool_search_path is superuser-only (PGC_SUSET): a
--    non-superuser must not be able to redirect where the backend
--    searches for tool binaries, since that is arbitrary code execution
--    as the postgres OS user.  provsql.fallback_compiler stays
--    user-settable: it only picks among a hard-coded name whitelist and
--    cannot point at an arbitrary binary.  The DO/EXCEPTION wrappers key
--    on the SQLSTATE so the expectation is locale-independent (the core
--    "permission denied" message is translated).
CREATE ROLE provsql_tsp_unpriv NOSUPERUSER;
SET ROLE provsql_tsp_unpriv;

DO $$
BEGIN
  SET provsql.tool_search_path = '/tmp/evil';
  RAISE NOTICE 'BUG: SET tool_search_path succeeded for a non-superuser';
EXCEPTION WHEN insufficient_privilege THEN
  RAISE NOTICE 'tool_search_path SET refused for non-superuser';
END $$;

DO $$
BEGIN
  PERFORM set_config('provsql.tool_search_path', '/tmp/evil', false);
  RAISE NOTICE 'BUG: set_config tool_search_path succeeded for a non-superuser';
EXCEPTION WHEN insufficient_privilege THEN
  RAISE NOTICE 'tool_search_path set_config refused for non-superuser';
END $$;

SET provsql.fallback_compiler = 'c2d';
SHOW provsql.fallback_compiler;

RESET ROLE;
DROP ROLE provsql_tsp_unpriv;

\! rm -rf /tmp/provsql_tsp_test
