#!/usr/bin/env bash

# A tool stub sets tool options, and an option such as an http: `url` is a
# template that can run commands. Running a stub is the consent for that, but
# `mise lock` reads the stubs mise.lock lists, so an untrusted project must not
# be able to have a file of its own locked that way. Tools declared by tasks
# are resolved by `mise lock` too, so their options need trust as well.

export MISE_LOCKFILE=1
export MISE_OFFLINE=1
export MISE_TRUSTED_CONFIG_PATHS=""
unset CI GITHUB_ACTIONS GITHUB_ACTION 2>/dev/null || true

mkdir -p proj/bin tasks safe/bin invalid/bin mise/bin
cd proj || exit 1
echo '[tools]' >mise.toml
echo 'tool-stubs = ["bin/widget"]' >mise.lock
cat >bin/widget <<'STUB'
tool = "http:widget"
version = "1.0.0"
url = "file:///nonexistent/{{ exec(command='touch $HOME/stub-option-ran') }}widget.tar.gz"
STUB
cp mise.lock mise.lock.orig

assert_fail_contains "MISE_YES=0 mise lock 2>&1" "not trusted"
assert_fail "test -e $HOME/stub-option-ran"
assert "diff mise.lock mise.lock.orig"

# Paranoid mode ties trust to each file's contents, so the stub needs its own.
# The `config` category alone is enough for that.
for paranoid in config 1; do
  MISE_PARANOID=$paranoid mise trust mise.toml
  assert_fail_contains "MISE_PARANOID=$paranoid MISE_YES=0 mise lock 2>&1" "bin/widget are not trusted"
  assert_fail "test -e $HOME/stub-option-ran"
  MISE_PARANOID=$paranoid mise trust bin/widget
  assert_succeed "MISE_PARANOID=$paranoid MISE_YES=0 mise lock"
  assert_succeed "test -e $HOME/stub-option-ran"
  rm "$HOME/stub-option-ran"
  MISE_PARANOID=$paranoid mise trust --untrust bin/widget
  MISE_PARANOID=$paranoid mise trust --untrust mise.toml
  cp mise.lock.orig mise.lock
done

# control: once the project is trusted the stub is locked, options included
mise trust mise.toml
assert_succeed "MISE_YES=0 mise lock"
assert_succeed "test -e $HOME/stub-option-ran"
assert_contains "cat mise.lock" '[[tools."http:widget"]]'

# Task tools with options need trust; a plain version does not.
cd ../tasks || exit 1
cat >mise.toml <<'EOF'
[tasks.build]
run = "make"

[tasks.build.tools."github:acme/widget"]
version = "latest"
api_url = "http://127.0.0.1:9/api/v3"
EOF
assert_fail_contains "MISE_YES=0 mise lock 2>&1" "not trusted"
assert_contains "MISE_YES=0 mise trust --show" "untrusted"

# Safe mode keeps a project's tool options inert, a stub's included, even in a
# trusted project. The stub's lock entries are left alone.
cd ../safe || exit 1
echo '[tools]' >mise.toml
echo 'tool-stubs = ["bin/plain"]' >mise.lock
cat >bin/plain <<'STUB'
tool = "http:plain"
version = "1.0.0"
url = "file:///nonexistent/plain.tar.gz"
STUB
mise trust mise.toml
(MISE_SAFE=1 mise lock >../safe.log 2>&1) || true
assert_not_contains "cat mise.lock" "nonexistent/plain"
assert_contains "cat ../safe.log" "in safe mode"
assert_succeed "MISE_SAFE=1 mise lock"
assert_contains "cat mise.lock" 'tool-stubs = ["bin/plain"]'
assert_succeed "mise lock"
assert_contains "cat mise.lock" "nonexistent/plain"

# A listed file that is not a stub is skipped without quoting it.
cd ../invalid || exit 1
echo '[tools]' >mise.toml
echo 'tool-stubs = ["bin/notes"]' >mise.lock
echo 'DEPLOY_TOKEN=s3cr3t-value' >bin/notes
mise trust mise.toml
assert_contains "mise lock 2>&1" "not a valid tool stub"
assert_not_contains "mise lock 2>&1" "s3cr3t-value"

# A checkout named `mise` with a config.toml covers the directory above it, but
# its lockfile's stub references stay inside the checkout.
cd ../mise || exit 1
echo '[tools]' >config.toml
cat >../outside-stub <<'STUB'
tool = "http:outside"
version = "1.0.0"
url = "file:///nonexistent/outside.tar.gz"
STUB
mkdir .git
echo 'tool-stubs = ["../outside-stub"]' >mise.lock
mise trust config.toml
assert_succeed "mise lock"
assert_not_contains "cat mise.lock" "outside"
# control: without the checkout boundary the parent is the project root
rmdir .git
echo 'tool-stubs = ["../outside-stub"]' >mise.lock
assert_succeed "mise lock"
assert_contains "cat mise.lock" "../outside-stub"
