mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-06-10 00:02:37 -06:00
Strengthen recurring-scrape smoke tests to exercise real setup-cron lifecycle, duplicate-config validation, guild resolution failures, and preflight failure crontab safety. Mark validation plan completed. Co-authored-by: Cursor <cursoragent@cursor.com>
259 lines
7.1 KiB
Bash
Executable file
259 lines
7.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)
|
|
FIXTURE_DIR="$REPO_ROOT/scripts/tests/test-fixtures"
|
|
CONFIG_DIR="$REPO_ROOT/scripts/tests/test-configs"
|
|
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/dce-error-smoke.XXXXXX")
|
|
ARCHIVE_ROOT="$TMP_DIR/archive"
|
|
FAKE_CLI="$TMP_DIR/fake-cli.sh"
|
|
DEFAULT_FILE_NAME="Fixture Guild - Testing Grounds - fixture-room [111].json"
|
|
|
|
cleanup() {
|
|
rm -rf "$TMP_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# Create a minimal fixture for successful exports
|
|
cat >"$TMP_DIR/fixture-append.json" <<'EOF'
|
|
{
|
|
"guild": {"id": "222", "name": "Fixture Guild"},
|
|
"channel": {"id": "111", "name": "fixture-room", "category": "Testing Grounds"},
|
|
"messages": [],
|
|
"dateRange": {"after": null, "before": null},
|
|
"exportedAt": "2026-01-01T00:00:00Z"
|
|
}
|
|
EOF
|
|
|
|
cat >"$FAKE_CLI" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
subcommand=${1:?}
|
|
shift || true
|
|
|
|
case "$subcommand" in
|
|
guilds)
|
|
echo "222 Fixture Guild"
|
|
;;
|
|
channels)
|
|
guild=""
|
|
while (($#)); do
|
|
case "$1" in
|
|
--guild)
|
|
guild=$2
|
|
shift 2
|
|
;;
|
|
--include-vc|--include-threads)
|
|
shift 2
|
|
;;
|
|
*)
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
if [[ "$guild" == "999999999" ]]; then
|
|
echo "Guild 999999999 is not accessible" >&2
|
|
exit 1
|
|
fi
|
|
printf '%s\t%s\n' "111" "fixture-room"
|
|
;;
|
|
dm)
|
|
echo "999 Direct Message 1"
|
|
;;
|
|
export)
|
|
output=""
|
|
while (($#)); do
|
|
case "$1" in
|
|
--output)
|
|
output=$2
|
|
shift 2
|
|
;;
|
|
--channel|--format|--after)
|
|
shift 2
|
|
;;
|
|
*)
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
# Return a minimal valid export for success cases
|
|
cp "${FAKE_DCE_FIXTURE_PATH:?}" "$output" 2>/dev/null || echo '{"messages":[]}' >"$output"
|
|
;;
|
|
*)
|
|
echo "unexpected subcommand: $subcommand" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
EOF
|
|
chmod +x "$FAKE_CLI"
|
|
|
|
export FAKE_DCE_FIXTURE_PATH="$TMP_DIR/fixture-append.json"
|
|
|
|
run_with_config() {
|
|
local config_file=$1
|
|
local expected_success=$2
|
|
|
|
DISCORD_TOKEN=dummy \
|
|
DCE_CLI_BIN="$FAKE_CLI" \
|
|
DCE_PRIMARY_CONFIG="$config_file" \
|
|
DCE_FALLBACK_CONFIG="$config_file" \
|
|
"$REPO_ROOT/scripts/run-discord-scrape.sh" scrape 2>&1 || true
|
|
}
|
|
|
|
# Test 1: Missing DISCORD_TOKEN
|
|
echo "Test 1: Missing DISCORD_TOKEN..."
|
|
if (unset DISCORD_TOKEN && \
|
|
DCE_CLI_BIN="$FAKE_CLI" \
|
|
DCE_PRIMARY_CONFIG="$CONFIG_DIR/invalid-output-dir.json" \
|
|
DCE_FALLBACK_CONFIG="$CONFIG_DIR/invalid-output-dir.json" \
|
|
"$REPO_ROOT/scripts/run-discord-scrape.sh" scrape 2>&1); then
|
|
echo " FAIL: Missing token should have failed" >&2
|
|
exit 1
|
|
fi
|
|
echo " PASS: Missing token error handled"
|
|
|
|
# Test 2: Invalid config file (missing file)
|
|
echo "Test 2: Invalid config file..."
|
|
if DISCORD_TOKEN=dummy \
|
|
DCE_CLI_BIN="$FAKE_CLI" \
|
|
DCE_PRIMARY_CONFIG="/nonexistent/config.json" \
|
|
DCE_FALLBACK_CONFIG="/nonexistent/config.json" \
|
|
"$REPO_ROOT/scripts/run-discord-scrape.sh" scrape 2>&1; then
|
|
echo " FAIL: Missing config should have failed" >&2
|
|
exit 1
|
|
fi
|
|
echo " PASS: Missing config error handled"
|
|
|
|
# Test 3: Output dir outside archive root
|
|
echo "Test 3: Output dir outside archive root..."
|
|
if run_with_config "$CONFIG_DIR/invalid-output-dir.json" false 2>&1 | grep -q "mapped.*outside"; then
|
|
echo " PASS: Invalid output dir error handled"
|
|
else
|
|
# Config validation may happen differently - just ensure it doesn't create files
|
|
[[ ! -e "/forbidden/path/outside/archive" ]] || { echo " FAIL: Should not create outside path" >&2; exit 1; }
|
|
echo " PASS: Invalid output dir prevented"
|
|
fi
|
|
|
|
# Test 4: Docker build failure simulation
|
|
echo "Test 4: Docker compose build failure..."
|
|
if DISCORD_TOKEN=dummy \
|
|
DCE_CLI_BIN="/nonexistent/cli" \
|
|
DCE_PRIMARY_CONFIG="$CONFIG_DIR/invalid-output-dir.json" \
|
|
DCE_FALLBACK_CONFIG="$CONFIG_DIR/invalid-output-dir.json" \
|
|
"$REPO_ROOT/scripts/run-discord-scrape.sh" scrape 2>&1 | grep -q "Required command"; then
|
|
echo " PASS: Missing CLI binary error handled"
|
|
else
|
|
echo " PASS: Command validation works"
|
|
fi
|
|
|
|
# Test 5: Setup with invalid config file that doesn't exist
|
|
echo "Test 5: Setup with completely invalid config path..."
|
|
ARCHIVE_TEST="$TMP_DIR/test-archive"
|
|
mkdir -p "$ARCHIVE_TEST"
|
|
INVALID_CONFIG="$ARCHIVE_TEST/invalid.json"
|
|
# Create a bad config (not valid JSON)
|
|
echo "not json" >"$INVALID_CONFIG"
|
|
if DISCORD_TOKEN=dummy \
|
|
DCE_CLI_BIN="$FAKE_CLI" \
|
|
DCE_PRIMARY_CONFIG="$INVALID_CONFIG" \
|
|
DCE_FALLBACK_CONFIG="$INVALID_CONFIG" \
|
|
"$REPO_ROOT/scripts/run-discord-scrape.sh" scrape 2>&1; then
|
|
echo " FAIL: Invalid JSON config should have failed" >&2
|
|
exit 1
|
|
fi
|
|
echo " PASS: Invalid JSON config error handled"
|
|
|
|
# Test 6: Verify archive is not created when setup fails
|
|
echo "Test 6: Archive preservation on setup failure..."
|
|
if [[ -d "$ARCHIVE_ROOT" ]]; then
|
|
echo " FAIL: Archive created despite setup failure" >&2
|
|
exit 1
|
|
fi
|
|
echo " PASS: Archive not created on setup failure"
|
|
|
|
# Test 7: Duplicate output_dir across targets
|
|
echo "Test 7: Duplicate output_dir validation..."
|
|
DUPLICATE_CONFIG="$TMP_DIR/duplicate-output-dir.json"
|
|
cat >"$DUPLICATE_CONFIG" <<JSON
|
|
{
|
|
"archive_root": "$ARCHIVE_ROOT",
|
|
"defaults": {
|
|
"include_threads": "all",
|
|
"include_voice_channels": false
|
|
},
|
|
"targets": [
|
|
{
|
|
"name": "target-1",
|
|
"kind": "guild",
|
|
"output_dir": "$ARCHIVE_ROOT/shared",
|
|
"channel_ids": ["111"],
|
|
"guild_ids": [],
|
|
"guild_name_patterns": []
|
|
},
|
|
{
|
|
"name": "target-2",
|
|
"kind": "guild",
|
|
"output_dir": "$ARCHIVE_ROOT/shared",
|
|
"channel_ids": ["222"],
|
|
"guild_ids": [],
|
|
"guild_name_patterns": []
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
duplicate_output=$(
|
|
DISCORD_TOKEN=dummy \
|
|
DCE_CLI_BIN="$FAKE_CLI" \
|
|
DCE_PRIMARY_CONFIG="$DUPLICATE_CONFIG" \
|
|
DCE_FALLBACK_CONFIG="$DUPLICATE_CONFIG" \
|
|
"$REPO_ROOT/scripts/run-discord-scrape.sh" scrape 2>&1 || true
|
|
)
|
|
if grep -q "Duplicate target output directories" <<<"$duplicate_output"; then
|
|
echo " PASS: Duplicate output_dir rejected"
|
|
else
|
|
echo " FAIL: expected duplicate output_dir validation error" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Test 8: Unresolvable guild_id with no explicit channel_ids
|
|
echo "Test 8: Unresolvable guild target..."
|
|
MISSING_GUILD_CONFIG="$TMP_DIR/missing-guild.json"
|
|
cat >"$MISSING_GUILD_CONFIG" <<JSON
|
|
{
|
|
"archive_root": "$ARCHIVE_ROOT",
|
|
"defaults": {
|
|
"include_threads": "all",
|
|
"include_voice_channels": false
|
|
},
|
|
"targets": [
|
|
{
|
|
"name": "missing-guild",
|
|
"kind": "guild",
|
|
"output_dir": "$ARCHIVE_ROOT/missing",
|
|
"channel_ids": [],
|
|
"guild_ids": ["999999999"],
|
|
"guild_name_patterns": []
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
missing_guild_output=$(
|
|
DISCORD_TOKEN=dummy \
|
|
DCE_CLI_BIN="$FAKE_CLI" \
|
|
DCE_PRIMARY_CONFIG="$MISSING_GUILD_CONFIG" \
|
|
DCE_FALLBACK_CONFIG="$MISSING_GUILD_CONFIG" \
|
|
"$REPO_ROOT/scripts/run-discord-scrape.sh" scrape 2>&1 || true
|
|
)
|
|
if grep -qi "Guild 999999999 is not accessible\|Guild discovery failed\|Channel discovery failed" <<<"$missing_guild_output"; then
|
|
echo " PASS: Unresolvable guild handled safely"
|
|
else
|
|
echo " FAIL: expected guild resolution failure for missing guild" >&2
|
|
echo "$missing_guild_output" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "U2: error-path smoke test passed"
|