morozovka-portal/tests/run_smoke.sh

447 lines
26 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# ══════════════════════════════════════════════════════════════════════
# Smoke-тесты Morozovka Portal — без GUI, через curl + psql
# Запуск: sudo bash tests/run_smoke.sh
# Вывод: цветной PASS/FAIL + tests/last_run.log
# ══════════════════════════════════════════════════════════════════════
set -uo pipefail
# ── Настройки ─────────────────────────────────────────────────────────
BASE="http://127.0.0.1:8000"
PG_HOST="127.0.0.1"
PG_USER="morozovka"
PG_PASS="${PG_PASS:-rf-slf-1945}"
PG_DB="morozovka_portal"
LOG_FILE="$(dirname "$0")/last_run.log"
COOKIE="/tmp/smoke_cookies_$$.txt"
TMP="/tmp/smoke_$$"
TS="$(date +%s)"
TEST_USER="smoke_test_${TS}"
TEST_PASS="SmokePass_${TS}!"
TEST_EMAIL="${TEST_USER}@test.local"
mkdir -p "$TMP"
: > "$LOG_FILE"
PASS=0
FAIL=0
TOTAL=0
START_TIME=$(date +%s)
# ── Цвета ─────────────────────────────────────────────────────────────
if [ -t 1 ]; then
C_GREEN='\033[0;32m'; C_RED='\033[0;31m'; C_YELLOW='\033[1;33m'
C_CYAN='\033[0;36m'; C_BOLD='\033[1m'; C_RESET='\033[0m'
else
C_GREEN=''; C_RED=''; C_YELLOW=''; C_CYAN=''; C_BOLD=''; C_RESET=''
fi
# ── Хелперы печати ────────────────────────────────────────────────────
section() {
local msg="$1"
echo
echo -e "${C_BOLD}═══ $msg ═══${C_RESET}"
echo "--- $msg ---" >> "$LOG_FILE"
}
info() {
echo -e "${C_CYAN} → $1${C_RESET}"
echo " INFO: $1" >> "$LOG_FILE"
}
pass() {
PASS=$((PASS+1)); TOTAL=$((TOTAL+1))
echo -e " ${C_GREEN}✓${C_RESET} $1"
echo " PASS: $1" >> "$LOG_FILE"
}
fail() {
FAIL=$((FAIL+1)); TOTAL=$((TOTAL+1))
echo -e " ${C_RED}✗${C_RESET} $1"
echo " ожидалось: $2"
echo " получено: $3"
echo " FAIL: $1" >> "$LOG_FILE"
echo " expected: $2" >> "$LOG_FILE"
echo " actual: $3" >> "$LOG_FILE"
}
# ── Хелперы проверок ──────────────────────────────────────────────────
check_status() {
local name="$1" expected="$2" actual="$3"
if [ "$actual" = "$expected" ]; then pass "$name"; else fail "$name" "$expected" "$actual"; fi
}
check_contains() {
local name="$1" pattern="$2" file="$3"
if grep -qF "$pattern" "$file" 2>/dev/null; then pass "$name"; else fail "$name" "contains '$pattern'" "не найдено в $file"; fi
}
check_not_empty() {
local name="$1" val="$2"
if [ -n "$val" ] && [ "$val" != "0" ]; then pass "$name"; else fail "$name" "not empty" "пусто"; fi
}
check_equal() {
local name="$1" expected="$2" actual="$3"
if [ "$actual" = "$expected" ]; then pass "$name"; else fail "$name" "$expected" "$actual"; fi
}
# ── psql ──────────────────────────────────────────────────────────────
psql_run() {
PGPASSWORD="$PG_PASS" psql -h "$PG_HOST" -U "$PG_USER" -d "$PG_DB" -tAc "$1" 2>/dev/null
}
psql_val() {
psql_run "$1" | tr -d ' \n'
}
# ── curl обёртки ──────────────────────────────────────────────────────
http_code_get() {
curl -sS -o "$2" -w "%{http_code}" -b "$COOKIE" "$1" 2>/dev/null
}
http_code_post_form() {
curl -sS -o "$2" -w "%{http_code}" -b "$COOKIE" -c "$COOKIE" -X POST "$1" -d "$3" 2>/dev/null
}
http_code_post_json() {
curl -sS -o "$2" -w "%{http_code}" -b "$COOKIE" -c "$COOKIE" -X POST "$1" \
-H "Content-Type: application/json" -d "$3" 2>/dev/null
}
# ── Cleanup ───────────────────────────────────────────────────────────
cleanup() {
info "Cleanup тестовых данных…"
local USER_ID="${TEST_USER_ID:-}"
if [ -n "$USER_ID" ]; then
# Удаляем строго по ID, ничего чужого не заденем
psql_run "DELETE FROM portalfile WHERE uploaded_by = $USER_ID" >/dev/null
psql_run "DELETE FROM fileversion WHERE uploaded_by = $USER_ID" >/dev/null
psql_run "DELETE FROM filefolderacl WHERE user_id = $USER_ID" >/dev/null
psql_run "DELETE FROM filespaceacl WHERE user_id = $USER_ID" >/dev/null
psql_run "DELETE FROM filefolder WHERE created_by = $USER_ID" >/dev/null
psql_run "DELETE FROM filespace WHERE owner_id = $USER_ID" >/dev/null
psql_run "DELETE FROM project WHERE owner_id = $USER_ID" >/dev/null
psql_run "DELETE FROM workshop WHERE lead_id = $USER_ID" >/dev/null
psql_run "DELETE FROM projecttask WHERE created_by = $USER_ID" >/dev/null
psql_run "DELETE FROM projectpost WHERE author_id = $USER_ID" >/dev/null
psql_run "DELETE FROM workshoppost WHERE author_id = $USER_ID" >/dev/null
psql_run "DELETE FROM useronboarding WHERE user_id = $USER_ID" >/dev/null
psql_run "DELETE FROM eventsubscriber WHERE user_id = $USER_ID" >/dev/null
psql_run "DELETE FROM usersession WHERE user_id = $USER_ID" >/dev/null
psql_run "DELETE FROM notification WHERE user_id = $USER_ID" >/dev/null
psql_run "DELETE FROM \"user\" WHERE id = $USER_ID" >/dev/null
fi
rm -f "$COOKIE"
rm -rf "$TMP"
info "Cleanup завершён"
}
trap cleanup EXIT
# ══════════════════════════════════════════════════════════════════════
# НАЧАЛО
# ══════════════════════════════════════════════════════════════════════
echo -e "${C_BOLD}╔══════════════════════════════════════════════════════╗${C_RESET}"
echo -e "${C_BOLD}║ Morozovka Portal — Smoke Tests (no GUI) ║${C_RESET}"
echo -e "${C_BOLD}╚══════════════════════════════════════════════════════╝${C_RESET}"
echo "Start: $(date -Is)"
echo "Base: $BASE"
echo "User: $TEST_USER"
echo "Log: $LOG_FILE"
echo
echo "Start: $(date -Is)" >> "$LOG_FILE"
echo "Base: $BASE" >> "$LOG_FILE"
echo "User: $TEST_USER" >> "$LOG_FILE"
# ── 0. Проверка, что сервис жив ───────────────────────────────────────
section "0. Health"
CODE=$(http_code_get "$BASE/healthz" "$TMP/healthz.json")
check_status "GET /healthz → 200" "200" "$CODE"
check_contains "healthz → status:ok" '"status":"ok"' "$TMP/healthz.json"
check_contains "healthz → db:ok" '"db":"ok"' "$TMP/healthz.json"
# ── 1. Публичные роуты ────────────────────────────────────────────────
section "1. Публичные роуты"
check_status "GET / → 200" "200" "$(http_code_get "$BASE/" "$TMP/index.html")"
check_status "GET /schedule → 200" "200" "$(http_code_get "$BASE/schedule" "$TMP/schedule.html")"
check_status "GET /feed → 200" "200" "$(http_code_get "$BASE/feed" "$TMP/feed.html")"
check_status "GET /projects → 200" "200" "$(http_code_get "$BASE/projects" "$TMP/projects.html")"
check_status "GET /workshops → 200" "200" "$(http_code_get "$BASE/workshops" "$TMP/workshops.html")"
check_status "GET /login → 200" "200" "$(http_code_get "$BASE/login" "$TMP/login.html")"
check_status "GET /register → 200" "200" "$(http_code_get "$BASE/register" "$TMP/register.html")"
check_status "GET /forgot_password → 200" "200" "$(http_code_get "$BASE/forgot_password" "$TMP/forgot.html")"
# Защищённые без cookie → 302 на login
rm -f "$COOKIE"
check_status "GET /files без cookie → 302" "302" "$(http_code_get "$BASE/files" "$TMP/files_noauth.html")"
check_status "GET /dashboard без cookie → 302" "302" "$(http_code_get "$BASE/dashboard" "$TMP/dash_noauth.html")"
check_status "GET /onboarding без cookie → 302" "302" "$(http_code_get "$BASE/onboarding" "$TMP/onb_noauth.html")"
# ── 2. Регистрация тестового пользователя ────────────────────────────
section "2. Регистрация пользователя"
CODE=$(http_code_post_form "$BASE/register" "$TMP/reg_resp.html" \
"username=${TEST_USER}&email=${TEST_EMAIL}&password=${TEST_PASS}&full_name=Smoke+Test&organization=%D0%9D%D0%9D%D0%93%D0%A3")
check_status "POST /register → 200" "200" "$CODE"
TEST_USER_ID=$(psql_val "SELECT id FROM \"user\" WHERE username = '$TEST_USER'")
check_not_empty "user создан в БД" "$TEST_USER_ID"
if [ -z "$TEST_USER_ID" ] || [ "$TEST_USER_ID" = "0" ]; then
echo -e "${C_RED}КРИТИЧНО: тестовый юзер не создан. Останавливаюсь.${C_RESET}"
exit 1
fi
info "user_id = $TEST_USER_ID"
# Промоут до admin (иначе require_role заблокирует создание проектов/МК)
psql_run "UPDATE \"user\" SET role = 'admin', is_verified = true WHERE id = $TEST_USER_ID" >/dev/null
check_equal "роль admin установлена" "admin" "$(psql_val "SELECT role FROM \"user\" WHERE id = $TEST_USER_ID")"
# ── 3. Логин ──────────────────────────────────────────────────────────
section "3. Логин"
rm -f "$COOKIE"
CODE=$(curl -sS -o "$TMP/login_resp.html" -w "%{http_code}" -c "$COOKIE" -X POST "$BASE/login" \
-d "username=${TEST_USER}&password=${TEST_PASS}" 2>/dev/null)
check_status "POST /login → 302" "302" "$CODE"
check_status "GET /dashboard после логина → 200" "200" "$(http_code_get "$BASE/dashboard" "$TMP/dashboard.html")"
check_contains "dashboard содержит 'Ближайшие события'" "Ближайшие события" "$TMP/dashboard.html"
# ── 4. Онбординг ──────────────────────────────────────────────────────
section "4. Онбординг"
check_status "GET /onboarding → 200" "200" "$(http_code_get "$BASE/onboarding" "$TMP/onb.html")"
CODE=$(http_code_post_json "$BASE/onboarding/mark" "$TMP/onb_mark.json" \
'{"key":"visited_schedule","value":true}')
check_status "POST /onboarding/mark → 200" "200" "$CODE"
CODE=$(http_code_post_form "$BASE/onboarding/complete" "$TMP/onb_complete.html" "")
check_status "POST /onboarding/complete → 302" "302" "$CODE"
# ── 5. Уведомления ────────────────────────────────────────────────────
section "5. Уведомления"
check_status "GET /api/notifications → 200" "200" "$(http_code_get "$BASE/api/notifications" "$TMP/notif.json")"
check_contains "notifications JSON содержит 'items'" '"items"' "$TMP/notif.json"
CODE=$(http_code_post_json "$BASE/api/notifications/read-all" "$TMP/notif_read.json" "{}")
check_status "POST /api/notifications/read-all → 200" "200" "$CODE"
# ── 6. Расписание + ассистент ────────────────────────────────────────
section "6. Расписание + ассистент"
check_status "GET /schedule → 200" "200" "$(http_code_get "$BASE/schedule" "$TMP/sched.html")"
check_contains "/schedule содержит 'cal-body'" 'cal-body' "$TMP/sched.html"
check_contains "/schedule содержит 'chat-toggle'" 'chat-toggle' "$TMP/sched.html"
# Ассистент — 5 интентов
for M in "Где сейчас?" "Когда следующая пара?" "Что сегодня?" "Он опаздывает?" "Как связаться?"; do
CODE=$(http_code_post_json "$BASE/api/assistant" "$TMP/assist.json" "{\"message\":\"$M\"}")
check_status "assistant '$M' → 200" "200" "$CODE"
check_contains " ответ содержит 'reply'" '"reply"' "$TMP/assist.json"
done
# ICS export
CODE=$(http_code_get "$BASE/schedule/export.ics" "$TMP/sched.ics")
check_status "GET /schedule/export.ics → 200" "200" "$CODE"
check_contains "ICS содержит 'BEGIN:VCALENDAR'" "BEGIN:VCALENDAR" "$TMP/sched.ics"
# ── 7. Проекты ────────────────────────────────────────────────────────
section "7. Проекты"
PROJ_NAME="SmokeTest Project $TS"
CODE=$(http_code_post_form "$BASE/projects/new" "$TMP/proj_new.html" \
"title=$(printf '%s' "$PROJ_NAME" | sed 's/ /+/g')&description=auto&status=in_progress&visibility=internal")
check_status "POST /projects/new → 302" "302" "$CODE"
PROJ_ID=$(psql_val "SELECT id FROM project WHERE title = '$PROJ_NAME' ORDER BY id DESC LIMIT 1")
check_not_empty "проект создан" "$PROJ_ID"
info "project_id = $PROJ_ID"
check_status "GET /projects/<id> → 200" "200" "$(http_code_get "$BASE/projects/$PROJ_ID" "$TMP/proj.html")"
check_status "GET /projects/<id>/board → 200" "200" "$(http_code_get "$BASE/projects/$PROJ_ID/board" "$TMP/board.html")"
# Задача
CODE=$(http_code_post_form "$BASE/projects/$PROJ_ID/tasks/new" "$TMP/task.html" \
"title=Smoke+Task&priority=medium&status=backlog")
check_status "POST /tasks/new → 302" "302" "$CODE"
TASK_ID=$(psql_val "SELECT id FROM projecttask WHERE project_id = $PROJ_ID ORDER BY id DESC LIMIT 1")
check_not_empty "задача создана" "$TASK_ID"
# ── 8. Мастер-классы ─────────────────────────────────────────────────
section "8. Мастер-классы"
WS_NAME="SmokeTest WS $TS"
CODE=$(http_code_post_form "$BASE/workshops/new" "$TMP/ws_new.html" \
"title=$(printf '%s' "$WS_NAME" | sed 's/ /+/g')&description=auto&category=other&difficulty=beginner&status=published&visibility=public&lead_id=$TEST_USER_ID")
check_status "POST /workshops/new → 302" "302" "$CODE"
WS_ID=$(psql_val "SELECT id FROM workshop WHERE title = '$WS_NAME' ORDER BY id DESC LIMIT 1")
check_not_empty "МК создан" "$WS_ID"
info "workshop_id = $WS_ID"
check_status "GET /workshops/<id> → 200" "200" "$(http_code_get "$BASE/workshops/$WS_ID" "$TMP/ws.html")"
# Регистрация на МК
CODE=$(http_code_post_form "$BASE/workshops/$WS_ID/register" "$TMP/ws_reg.html" "")
check_status "POST /workshops/<id>/register → 302" "302" "$CODE"
REG_COUNT=$(psql_val "SELECT COUNT(*) FROM workshopregistration WHERE workshop_id = $WS_ID AND user_id = $TEST_USER_ID")
check_equal "регистрация в БД" "1" "$REG_COUNT"
# ── 9. Файлы v2.0 ────────────────────────────────────────────────────
section "9. Файлы v2.0 — пространство/папка/файл/версии/ACL/move/delete"
SPACE_NAME="SmokeTest Space $TS"
CODE=$(http_code_post_form "$BASE/files/space/new" "$TMP/sp_new.html" \
"name=$(printf '%s' "$SPACE_NAME" | sed 's/ /+/g')&description=auto&visibility=internal")
check_status "POST /files/space/new → 302" "302" "$CODE"
SPACE_ID=$(psql_val "SELECT id FROM filespace WHERE name = '$SPACE_NAME' ORDER BY id DESC LIMIT 1")
check_not_empty "пространство создано" "$SPACE_ID"
info "space_id = $SPACE_ID"
check_status "GET /files/space/<id> → 200" "200" "$(http_code_get "$BASE/files/space/$SPACE_ID" "$TMP/space.html")"
# Папка
FOLDER_NAME="TestFolder_$TS"
CODE=$(http_code_post_form "$BASE/files/folder/new" "$TMP/folder_new.html" \
"space_id=$SPACE_ID&parent_id=&name=$FOLDER_NAME&description=auto")
check_status "POST /files/folder/new → 302" "302" "$CODE"
FOLDER_ID=$(psql_val "SELECT id FROM filefolder WHERE name = '$FOLDER_NAME' AND space_id = $SPACE_ID")
check_not_empty "папка создана" "$FOLDER_ID"
info "folder_id = $FOLDER_ID"
check_status "GET /files/folder/<id> → 200" "200" "$(http_code_get "$BASE/files/folder/$FOLDER_ID" "$TMP/folder.html")"
# Файл
echo "test content v1 — $(date -Is)" > "$TMP/upload.txt"
CODE=$(curl -sS -o "$TMP/up_resp.html" -w "%{http_code}" -b "$COOKIE" -c "$COOKIE" \
-X POST "$BASE/files/upload" \
-F "file=@$TMP/upload.txt;filename=smoke_test_$TS.txt" \
-F "space_id=$SPACE_ID" -F "folder_id=$FOLDER_ID" 2>/dev/null)
check_status "POST /files/upload → 302" "302" "$CODE"
FILE_ID=$(psql_val "SELECT id FROM portalfile WHERE space_id = $SPACE_ID AND folder_id = $FOLDER_ID ORDER BY id DESC LIMIT 1")
check_not_empty "файл создан" "$FILE_ID"
info "file_id = $FILE_ID"
# Проверка FileVersion(1)
FILE_VERSION=$(psql_val "SELECT version FROM portalfile WHERE id = $FILE_ID")
check_equal "file.version = 1" "1" "$FILE_VERSION"
CUR_VER_ID=$(psql_val "SELECT current_version_id FROM portalfile WHERE id = $FILE_ID")
check_not_empty "current_version_id задан" "$CUR_VER_ID"
VER_COUNT=$(psql_val "SELECT COUNT(*) FROM fileversion WHERE file_id = $FILE_ID")
check_equal "fileversion count = 1" "1" "$VER_COUNT"
# Скачать текущую
check_status "GET /files/download/<id> → 200" "200" "$(http_code_get "$BASE/files/download/$FILE_ID" "$TMP/dl1.txt")"
check_contains "скачано v1" "test content v1" "$TMP/dl1.txt"
# История
check_status "GET /files/<id>/history → 200" "200" "$(http_code_get "$BASE/files/$FILE_ID/history" "$TMP/history.html")"
check_contains "история содержит 'v1'" "v1" "$TMP/history.html"
# Новая версия
echo "test content v2 — $(date -Is)" > "$TMP/upload_v2.txt"
CODE=$(curl -sS -o "$TMP/newv.html" -w "%{http_code}" -b "$COOKIE" -c "$COOKIE" \
-X POST "$BASE/files/$FILE_ID/new_version" \
-F "file=@$TMP/upload_v2.txt;filename=smoke_test_$TS.txt" \
-F "comment=Auto v2" 2>/dev/null)
check_status "POST /files/<id>/new_version → 302" "302" "$CODE"
check_equal "version = 2" "2" "$(psql_val "SELECT version FROM portalfile WHERE id = $FILE_ID")"
check_equal "fileversion count = 2" "2" "$(psql_val "SELECT COUNT(*) FROM fileversion WHERE file_id = $FILE_ID")"
# Скачать v1
V1_ID=$(psql_val "SELECT id FROM fileversion WHERE file_id = $FILE_ID AND version_num = 1")
check_status "GET /files/<id>/version/<v1>/download → 200" "200" \
"$(http_code_get "$BASE/files/$FILE_ID/version/$V1_ID/download" "$TMP/dl_v1.txt")"
check_contains "скачано содержимое v1" "test content v1" "$TMP/dl_v1.txt"
# Скачать текущую (v2)
check_status "GET /files/download/<id> (v2) → 200" "200" "$(http_code_get "$BASE/files/download/$FILE_ID" "$TMP/dl_v2.txt")"
check_contains "скачано v2" "test content v2" "$TMP/dl_v2.txt"
# ACL пространства
CODE=$(http_code_post_form "$BASE/files/space/$SPACE_ID/acl/add" "$TMP/acl.html" \
"username=$TEST_USER&role=editor")
check_status "POST /files/space/<id>/acl/add → 302" "302" "$CODE"
# (нельзя добавить себя-владельца, роут вернёт redirect без изменений — это OK, проверяем что не 500)
ACL_PAGE_CODE=$(http_code_get "$BASE/files/space/$SPACE_ID/acl" "$TMP/acl_page.html")
check_status "GET /files/space/<id>/acl → 200" "200" "$ACL_PAGE_CODE"
# Move: файл из пространства в NULL
CODE=$(http_code_post_form "$BASE/files/$FILE_ID/move" "$TMP/move1.html" \
"space_id=&folder_id=")
check_status "POST /files/<id>/move → NULL → 302" "302" "$CODE"
check_equal "space_id обнулён" "" "$(psql_val "SELECT space_id FROM portalfile WHERE id = $FILE_ID AND space_id IS NOT NULL")"
check_equal "version сброшен в 1" "1" "$(psql_val "SELECT version FROM portalfile WHERE id = $FILE_ID")"
check_equal "current_version_id = NULL" "" "$(psql_val "SELECT current_version_id FROM portalfile WHERE id = $FILE_ID AND current_version_id IS NOT NULL")"
check_equal "fileversion удалены" "0" "$(psql_val "SELECT COUNT(*) FROM fileversion WHERE file_id = $FILE_ID")"
# Move обратно в пространство
CODE=$(http_code_post_form "$BASE/files/$FILE_ID/move" "$TMP/move2.html" \
"space_id=$SPACE_ID&folder_id=$FOLDER_ID")
check_status "POST /files/<id>/move → обратно в space → 302" "302" "$CODE"
check_equal "space_id восстановлен" "$SPACE_ID" "$(psql_val "SELECT space_id FROM portalfile WHERE id = $FILE_ID")"
check_equal "fileversion count = 1 (миграция)" "1" "$(psql_val "SELECT COUNT(*) FROM fileversion WHERE file_id = $FILE_ID")"
# Удаление файла
CODE=$(http_code_post_form "$BASE/files/$FILE_ID/delete" "$TMP/fdel.html" "next=/files/space/$SPACE_ID")
check_status "POST /files/<id>/delete → 302" "302" "$CODE"
FILE_EXISTS=$(psql_val "SELECT COUNT(*) FROM portalfile WHERE id = $FILE_ID")
check_equal "файл удалён из БД" "0" "$FILE_EXISTS"
# Удаление ПАПКИ (главная проверка!)
CODE=$(http_code_post_form "$BASE/files/folder/$FOLDER_ID/delete" "$TMP/folder_del.html" "")
check_status "POST /files/folder/<id>/delete → 302" "302" "$CODE"
FOLDER_EXISTS=$(psql_val "SELECT COUNT(*) FROM filefolder WHERE id = $FOLDER_ID")
check_equal "папка удалена из БД" "0" "$FOLDER_EXISTS"
# Удаление ПРОСТРАНСТВА
CODE=$(http_code_post_form "$BASE/files/space/$SPACE_ID/delete" "$TMP/space_del.html" "")
check_status "POST /files/space/<id>/delete → 302" "302" "$CODE"
SPACE_EXISTS=$(psql_val "SELECT COUNT(*) FROM filespace WHERE id = $SPACE_ID")
check_equal "пространство удалено из БД" "0" "$SPACE_EXISTS"
# ── 10. Вебхук ───────────────────────────────────────────────────────
section "10. Вебхук Gitea"
CODE=$(curl -sS -o "$TMP/wh_bad.json" -w "%{http_code}" -X POST "$BASE/webhooks/gitea" \
-H "X-Gitea-Event: push" -H "X-Gitea-Signature: DEADBEEF" \
-H "Content-Type: application/json" -d '{}' 2>/dev/null)
check_status "POST /webhooks/gitea (плохая подпись) → 401" "401" "$CODE"
# Валидная подпись
SECRET=$(grep '^GITEA_WEBHOOK_SECRET=' /opt/morozovka-portal/.env 2>/dev/null | cut -d= -f2- | tr -d '"' | tr -d "'")
if [ -n "$SECRET" ]; then
BODY='{"repository":{"full_name":"test/nonexistent"},"pusher":{"username":"smoke"},"ref":"refs/heads/main","commits":[]}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" 2>/dev/null | awk '{print $NF}')
CODE=$(curl -sS -o "$TMP/wh_ok.json" -w "%{http_code}" -X POST "$BASE/webhooks/gitea" \
-H "X-Gitea-Event: push" -H "X-Gitea-Signature: $SIG" \
-H "Content-Type: application/json" -d "$BODY" 2>/dev/null)
check_status "POST /webhooks/gitea (валидная подпись) → 200" "200" "$CODE"
else
info "GITEA_WEBHOOK_SECRET не найден в .env — пропускаю валидную проверку"
fi
# ── 11. Логаут ────────────────────────────────────────────────────────
section "11. Логаут"
check_status "GET /logout → 302" "302" "$(http_code_get "$BASE/logout" "$TMP/logout.html")"
rm -f "$COOKIE"
check_status "GET /dashboard после logout → 302" "302" "$(http_code_get "$BASE/dashboard" "$TMP/dash_after.html")"
# ══════════════════════════════════════════════════════════════════════
# ИТОГ
# ══════════════════════════════════════════════════════════════════════
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
echo
echo -e "${C_BOLD}╔══════════════════════════════════════════════════════╗${C_RESET}"
if [ "$FAIL" -eq 0 ]; then
echo -e "${C_BOLD}║${C_RESET} ${C_GREEN}${C_BOLD}ВСЕ ТЕСТЫ ПРОШЛИ${C_RESET} ${C_BOLD}║${C_RESET}"
else
echo -e "${C_BOLD}║${C_RESET} ${C_RED}${C_BOLD}ЕСТЬ ПРОВАЛЫ: $FAIL${C_RESET} ${C_BOLD}║${C_RESET}"
fi
echo -e "${C_BOLD}╚══════════════════════════════════════════════════════╝${C_RESET}"
echo -e " ${C_GREEN}PASS:${C_RESET} $PASS"
echo -e " ${C_RED}FAIL:${C_RESET} $FAIL"
echo -e " Всего: $TOTAL"
echo -e " Длительность: ${DURATION}s"
echo -e " Лог: ${C_CYAN}$LOG_FILE${C_RESET}"
echo "" >> "$LOG_FILE"
echo "=== ИТОГ ===" >> "$LOG_FILE"
echo "PASS: $PASS" >> "$LOG_FILE"
echo "FAIL: $FAIL" >> "$LOG_FILE"
echo "TOTAL: $TOTAL" >> "$LOG_FILE"
echo "DURATION: ${DURATION}s" >> "$LOG_FILE"
echo "End: $(date -Is)" >> "$LOG_FILE"
if [ "$FAIL" -eq 0 ]; then exit 0; else exit 1; fi