191 lines
7.7 KiB
Bash
Executable File
191 lines
7.7 KiB
Bash
Executable File
#!/bin/bash
|
|
# Cupcake Policy Test Suite for NoteFlow
|
|
# Tests the .cupcake/policies/opencode/ policy rules
|
|
set -euo pipefail
|
|
SCRIPT_DIR="$(dirname "$0")"
|
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
cd "$SCRIPT_DIR"
|
|
|
|
TMP_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
|
|
PASSED=0
|
|
FAILED=0
|
|
FAILED_TESTS=""
|
|
|
|
CWD="$REPO_ROOT"
|
|
|
|
run_test() {
|
|
local name="$1"
|
|
local expected="$2"
|
|
local tool="$3"
|
|
local args_json="$4"
|
|
|
|
local test_file="$TMP_DIR/${name}.json"
|
|
|
|
# Build JSON using jq for proper escaping
|
|
jq -n \
|
|
--arg tool "$tool" \
|
|
--argjson args "$args_json" \
|
|
--arg cwd "$CWD" \
|
|
'{
|
|
hook_event_name: "PreToolUse",
|
|
tool: $tool,
|
|
args: $args,
|
|
session_id: "test",
|
|
cwd: $cwd
|
|
}' > "$test_file"
|
|
|
|
local output
|
|
output=$(cd "$REPO_ROOT" && cupcake eval --harness opencode < "$test_file" 2>/dev/null | grep -E "^\{" | head -1) || true
|
|
|
|
local decision
|
|
decision=$(echo "$output" | jq -r '.decision // "error"' 2>/dev/null) || decision="error"
|
|
|
|
if [[ "$decision" == "$expected" ]]; then
|
|
echo "PASS: $name"
|
|
PASSED=$((PASSED + 1))
|
|
else
|
|
echo "FAIL: $name (expected=$expected, got=$decision)"
|
|
FAILED=$((FAILED + 1))
|
|
FAILED_TESTS="${FAILED_TESTS}\n - $name"
|
|
fi
|
|
}
|
|
|
|
echo "Running Cupcake Policy Tests..."
|
|
echo "================================"
|
|
|
|
# === block_no_verify.rego ===
|
|
run_test "block_no_verify_deny" "deny" "bash" \
|
|
'{"command": "git commit --no-verify"}'
|
|
|
|
run_test "block_no_verify_allow" "allow" "bash" \
|
|
'{"command": "git commit -m fix"}'
|
|
|
|
# === block_makefile_edit.rego ===
|
|
run_test "block_makefile_edit_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/Makefile\", \"new_string\": \"test:\"}"
|
|
|
|
# === block_linter_config_edit.rego ===
|
|
run_test "block_linter_config_pyproject_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/pyproject.toml\", \"new_string\": \"[tool]\"}"
|
|
|
|
run_test "block_linter_config_pyrightconfig_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/pyrightconfig.json\", \"new_string\": \"{}\"}"
|
|
|
|
run_test "block_linter_config_biome_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/client/biome.json\", \"new_string\": \"{}\"}"
|
|
|
|
run_test "block_linter_config_tsconfig_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/client/tsconfig.json\", \"new_string\": \"{}\"}"
|
|
|
|
# === block_tests_quality_edit.rego ===
|
|
run_test "block_tests_quality_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/tests/quality/test_smells.py\", \"new_string\": \"#\"}"
|
|
|
|
# === warn_baselines_edit.rego ===
|
|
run_test "warn_baselines_edit_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/tests/quality/baselines.json\", \"new_string\": \"{}\"}"
|
|
|
|
# === block_code_quality_test_edit.rego ===
|
|
run_test "block_code_quality_test_edit_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/src/test/code-quality.test.ts\", \"new_string\": \"//\"}"
|
|
|
|
run_test "block_code_quality_test_bash_deny" "deny" "bash" \
|
|
'{"command": "sed -i s/x/y/ src/test/code-quality.test.ts"}'
|
|
|
|
# === block_biome_ignore.rego ===
|
|
run_test "block_biome_ignore_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/client/src/App.tsx\", \"new_string\": \"// biome-ignore lint: reason\"}"
|
|
|
|
run_test "block_ts_ignore_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/client/src/App.tsx\", \"new_string\": \"// @ts-ignore\"}"
|
|
|
|
run_test "block_biome_ignore_allow" "allow" "edit" \
|
|
"{\"file_path\": \"$CWD/client/src/App.tsx\", \"new_string\": \"const x = 1;\"}"
|
|
|
|
run_test "block_biome_ignore_bash_deny" "deny" "bash" \
|
|
'{"command": "echo // @ts-ignore >> client/src/App.tsx"}'
|
|
|
|
# === block_magic_numbers.rego ===
|
|
run_test "block_magic_numbers_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/service.py\", \"new_string\": \"timeout = 30\"}"
|
|
|
|
run_test "block_magic_numbers_constants_allow" "allow" "edit" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/constants.py\", \"new_string\": \"TIMEOUT = 30\"}"
|
|
|
|
# === ban_stdlib_logger.rego ===
|
|
run_test "ban_stdlib_logger_deny" "deny" "write" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/test.py\", \"content\": \"import logging\\nlogger = logging.getLogger(__name__)\"}"
|
|
|
|
run_test "ban_stdlib_logger_allow" "allow" "write" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/test.py\", \"content\": \"from noteflow.log import get_logger\"}"
|
|
|
|
# === prevent_any_type.rego ===
|
|
run_test "prevent_any_type_deny" "deny" "write" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/types.py\", \"content\": \"from typing import Any\\ndef f(x: Any): pass\"}"
|
|
|
|
run_test "prevent_any_type_allow" "allow" "write" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/types.py\", \"content\": \"from typing import TypeVar\\nT = TypeVar(\\\"T\\\")\"}"
|
|
|
|
run_test "prevent_any_type_edit_newString_deny" "deny" "edit" \
|
|
"{\"filePath\": \"$CWD/test_any_import.py\", \"oldString\": \"def store(items: dict[str, Any]) -> None:\\n pass\", \"newString\": \"from typing import Any\\n\\n\"}"
|
|
|
|
# === prevent_type_suppression.rego ===
|
|
run_test "prevent_type_suppression_type_ignore_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/service.py\", \"new_string\": \"x = val # type: ignore\"}"
|
|
|
|
run_test "prevent_type_suppression_pyright_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/service.py\", \"new_string\": \"x = val # pyright: ignore\"}"
|
|
|
|
run_test "prevent_type_suppression_noqa_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/service.py\", \"new_string\": \"from x import * # noqa\"}"
|
|
|
|
run_test "prevent_type_suppression_ruff_noqa_deny" "deny" "edit" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/service.py\", \"new_string\": \"# ruff: noqa: F401, E501\"}"
|
|
|
|
run_test "prevent_type_suppression_allow" "allow" "edit" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/service.py\", \"new_string\": \"x: str = val\"}"
|
|
|
|
# === block_broad_exception_handler.rego ===
|
|
run_test "block_broad_exception_handler_deny" "deny" "write" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/handler.py\", \"content\": \"try:\\n x()\\nexcept Exception as e:\\n logger.error(e)\"}"
|
|
|
|
run_test "block_broad_exception_handler_allow" "allow" "write" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/handler.py\", \"content\": \"try:\\n x()\\nexcept ValueError as e:\\n raise\"}"
|
|
|
|
# === block_silent_none_return.rego ===
|
|
run_test "block_silent_none_return_deny" "deny" "write" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/fetch.py\", \"content\": \"try:\\n return get()\\nexcept NetworkError as e:\\n logger.error(e)\\n return None\"}"
|
|
|
|
# === block_datetime_now_fallback.rego ===
|
|
run_test "block_datetime_now_fallback_deny" "deny" "write" \
|
|
"{\"file_path\": \"$CWD/src/noteflow/time.py\", \"content\": \"def ts():\\n return datetime.now()\"}"
|
|
|
|
# === block_assertion_roulette.rego ===
|
|
run_test "block_assertion_roulette_deny" "deny" "write" \
|
|
"{\"file_path\": \"$CWD/tests/test_example.py\", \"content\": \"assert a == 1\\nassert b == 2\"}"
|
|
|
|
# === block_test_loops.rego ===
|
|
run_test "block_test_loops_deny" "deny" "write" \
|
|
"{\"file_path\": \"$CWD/tests/test_items.py\", \"content\": \"def test_all():\\n for i in items:\\n assert i.valid\"}"
|
|
|
|
# === block_duplicate_fixtures.rego ===
|
|
run_test "block_duplicate_fixtures_deny" "deny" "write" \
|
|
"{\"file_path\": \"$CWD/tests/unit/test_x.py\", \"content\": \"import pytest\\n\\n@pytest.fixture\\ndef mock_uow():\\n return None\"}"
|
|
|
|
run_test "block_duplicate_fixtures_conftest_allow" "allow" "write" \
|
|
"{\"file_path\": \"$CWD/tests/conftest.py\", \"content\": \"import pytest\\n\\n@pytest.fixture\\ndef custom():\\n return None\"}"
|
|
|
|
echo ""
|
|
echo "========================================"
|
|
echo "Results: $PASSED passed, $FAILED failed"
|
|
echo "========================================"
|
|
|
|
if [[ $FAILED -gt 0 ]]; then
|
|
echo -e "\nFailed tests:$FAILED_TESTS"
|
|
exit 1
|
|
fi
|
|
|
|
echo "All tests passed!"
|