П'ять паралельних задач. Найцінніше в них — не можливості, а знайдене.
0069 БІЛІНГ. Аудит 0009 показав, що перевірка ліміту не спрацювала б
жодного разу: isPlanLimit шукала слово «ліміт», а тригер писав
"device limit reached" англійською. Перше ж досягнення стелі дало б
клієнту 500 замість пояснення. Плюс три діри: тригер лише на INSERT
(стеля в 15 обходилась за чотири дії через архів), max_maps/max_agents/
max_users не перевіряло ніщо — тобто рівно те, чим відрізняються плани,
і license_keys була закрита політикою tenant_isolation з 0011, хоча
tenant_id там NULLABLE навмисно: головний сценарій self-hosted був
недосяжний.
Після закінчення ліцензії не вимикається нічого — замерзає лише ріст.
Моніторинг, що перестав моніторити через несплачений рахунок, це
аварія в мережі клієнта, спричинена нами.
0070 SLA. Джерелом обрано ts.icmp_1h, а не device_status_history:
остання не вміє сказати «ми не знали» — перехід пишеться лише при
зміні стану, тож доба мовчання зонда виглядає як доба роботи. Час
розкладено на чотири частини, і «немає даних» не додається ні до чого;
замість вибору між двома брехнями звіт каже, яку частку періоду він
бачив. Закритий період тримає тригер, а не домовленість у Go.
0071 ВІДПОВІДНІСТЬ. 20 правил, кожне прив'язане до родини: об'єднаний
вираз, що покриває Cisco й не покриває MikroTik, дав би «0 порушень» і
сховав сліпу пляму. Вендор не входить у перелік, доки для нього немає
зразка конфігу в тесті. TestBuiltinRulesAreNotAlwaysGreen вимагає, щоб
у кожного правила був конфіг, де воно спрацювало, І де ні.
ПІСОЧНИЦЯ УСТАНОВНИКА — та сама установка в ізольованому проєкті
compose. Знайшла дві справжні вади з трьох спроб:
* healthcheck бази ходив unix-сокетом, а споживачі по TCP. При
первинній ініціалізації Postgres слухає лише сокет — compose
вважав базу здоровою, migrate отримував connection refused. На
створеній базі цієї фази немає, тож вада чекала на першого клієнта;
* у білому переліку модулів API не було traps і filecfg — зонд із
приймачем трапів неможливо було зареєструвати взагалі.
ТЕСТИ СТОРІНОК: 137 → 252. Мережевий шар, права доступу, незворотні
дії, фільтри з адресного рядка. Підмінюється лише fetch і WebSocket —
api/client.ts працює справжній.
141 lines
6.8 KiB
Bash
141 lines
6.8 KiB
Bash
#!/usr/bin/env sh
|
||
#
|
||
# Усі перевірки NetPulse однією командою.
|
||
#
|
||
# Навіщо окремий скрипт, коли є .forgejo/workflows/ci.yml: раннера
|
||
# Forgejo досі немає, тобто CI не запускався жодного разу. Перевірка, яку
|
||
# нікому виконати, не ловить нічого — а та сама послідовність, запущена
|
||
# руками перед комітом, ловить уже сьогодні. Коли раннер зʼявиться, CI
|
||
# викликатиме ті самі кроки; розходитись їм нема з чого.
|
||
#
|
||
# Запуск:
|
||
# sh scripts/check.sh # усе
|
||
# sh scripts/check.sh web # лише веб
|
||
# sh scripts/check.sh go # лише server + agent
|
||
# sh scripts/check.sh repo # лише перевірки репозиторію
|
||
#
|
||
# На Windows — з Git Bash; PowerShell цей файл не виконує.
|
||
#
|
||
# Скрипт НЕ зупиняється на першій невдачі навмисно: інакше, щоб
|
||
# побачити три поламані місця, довелось би прогнати його тричі.
|
||
# Ненульовий код виходу все одно повертається — рівно для гачка
|
||
# pre-push і для CI.
|
||
|
||
set -u
|
||
|
||
# CDPATH= — не помилка з пробілом, а гасіння CDPATH: якщо він виставлений
|
||
# в оточенні, cd мовчки піде не туди, і скрипт перевірятиме чужий каталог.
|
||
# shellcheck disable=SC1007
|
||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||
cd "$ROOT" || exit 1
|
||
|
||
FAILED=""
|
||
PASSED=""
|
||
|
||
# Прогін одного кроку. Назва потрібна для підсумку: список «що саме
|
||
# впало» у кінці цінніший за чотириста рядків виводу вище.
|
||
step() {
|
||
name=$1
|
||
shift
|
||
printf '\n\033[1m== %s\033[0m\n' "$name"
|
||
if "$@"; then
|
||
PASSED="$PASSED|$name"
|
||
else
|
||
FAILED="$FAILED|$name"
|
||
fi
|
||
}
|
||
|
||
# gofmt -l друкує список файлів, а не код виходу: без явної перевірки
|
||
# порожнечі крива форма проїжджає в main непоміченою.
|
||
gofmt_check() {
|
||
bad=$(gofmt -l "$1")
|
||
if [ -n "$bad" ]; then
|
||
echo "не відформатовано (виправити: gofmt -w $1):"
|
||
echo "$bad"
|
||
# На Windows цей крок червоніє ще й від CRLF у робочій копії: gofmt
|
||
# вважає \r частиною рядка й пропонує переписати файл цілком. У
|
||
# репозиторії лежить LF (див. .gitattributes), тож на Linux-раннері
|
||
# той самий файл зелений. Різницю треба назвати вголос: інакше її
|
||
# «виправлять» через gofmt -w і закомітять зіпсовані переноси.
|
||
echo "якщо різниця в цілому файлі — це CRLF у робочій копії:"
|
||
echo " git ls-files --eol $1 | git add --renormalize ."
|
||
return 1
|
||
fi
|
||
echo "форма в порядку"
|
||
}
|
||
|
||
# Зонд їде на чуже залізо: перевіряємо, що збирається під усі платформи,
|
||
# які обіцяємо, а не лише під ту, де запустили скрипт.
|
||
agent_cross() {
|
||
out=${TMPDIR:-/tmp}/netpulse-crossbuild
|
||
mkdir -p "$out" || return 1
|
||
for target in linux/amd64 linux/arm64 linux/arm windows/amd64 darwin/arm64; do
|
||
os=${target%/*}
|
||
arch=${target#*/}
|
||
echo "-- $os/$arch"
|
||
( cd agent && CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" go build -trimpath \
|
||
-o "$out/netpulse-agent-$os-$arch" ./cmd/netpulse-agent ) || return 1
|
||
done
|
||
rm -rf "$out"
|
||
}
|
||
|
||
go_in() { ( cd "$1" && shift && "$@" ); }
|
||
|
||
check_go() {
|
||
step "server: gofmt" gofmt_check server
|
||
step "server: vet" go_in server go vet ./...
|
||
# Тести проти бази самі пропускаються без NETPULSE_TEST_DSN — локальний
|
||
# прогін не має ні піднімати Postgres, ні тим паче чіпати бойову базу.
|
||
step "server: тести" go_in server go test ./...
|
||
step "agent: gofmt" gofmt_check agent
|
||
step "agent: vet" go_in agent go vet ./...
|
||
step "agent: тести" go_in agent go test ./...
|
||
step "agent: крос-збірка" agent_cross
|
||
}
|
||
|
||
# Перевірки самого репозиторію — не коду, а того, як він лежить.
|
||
#
|
||
# Обидві ловлять поломки, яких не видно в diff і які проявляються вже на
|
||
# стенді: розходження каталогу профілів зі згенерованою міграцією і CRLF,
|
||
# що змінює sha256 тієї ж міграції. Чому саме так — у шапках
|
||
# scripts/check-eol.sh і db/profiles/build.py.
|
||
#
|
||
# Тут вони без --strict: у робочій копії на Windows CRLF цілком буває, і
|
||
# це попередження, а не привід не дати закомітити. У CI той самий скрипт
|
||
# викликається з --strict — там свіжий клон на Linux, і CRLF означав би
|
||
# зламане викачування.
|
||
check_repo() {
|
||
step "репозиторій: закінчення рядків" sh scripts/check-eol.sh
|
||
# python3 на Linux, python у Git Bash на Windows: різні імена одного
|
||
# й того самого, і вгадувати не треба — беремо той, що є.
|
||
py=python3
|
||
command -v python3 >/dev/null 2>&1 || py=python
|
||
step "репозиторій: каталог профілів" "$py" db/profiles/build.py --check
|
||
}
|
||
|
||
check_web() {
|
||
# npm ci лише коли залежностей немає: на кожному прогоні він викидає
|
||
# node_modules і ставить наново — хвилини замість секунд.
|
||
if [ ! -d web/node_modules ]; then
|
||
step "web: залежності" go_in web npm ci
|
||
fi
|
||
step "web: типи" go_in web npx tsc --noEmit
|
||
step "web: тести" go_in web npx vitest run
|
||
step "web: збірка" go_in web npx vite build
|
||
}
|
||
|
||
case "${1:-all}" in
|
||
web) check_web ;;
|
||
go) check_go ;;
|
||
repo) check_repo ;;
|
||
all) check_repo; check_go; check_web ;;
|
||
*) echo "невідомий набір: $1 (є: all, repo, go, web)"; exit 2 ;;
|
||
esac
|
||
|
||
printf '\n\033[1m== підсумок\033[0m\n'
|
||
echo "$PASSED" | tr '|' '\n' | sed '/^$/d' | sed 's/^/ ok /'
|
||
if [ -n "$FAILED" ]; then
|
||
echo "$FAILED" | tr '|' '\n' | sed '/^$/d' | sed 's/^/ ВПАЛО /'
|
||
exit 1
|
||
fi
|
||
echo "усе зелене"
|