268 lines
7.4 KiB
Bash
Executable File
268 lines
7.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
COMMON_PLAT_DIR="${COMMON_PLAT_DIR:-${ROOT_DIR}/../learning_ai/learning_ai_common_plat}"
|
|
|
|
NOTELETT_URL="${NOTELETT_URL:-http://localhost:4016}"
|
|
NOTELETT_API_URL="${NOTELETT_API_URL:-${NOTELETT_URL}/api}"
|
|
PLATFORM_URL="${PLATFORM_URL:-http://localhost:4003}"
|
|
EXTRACTION_URL="${EXTRACTION_URL:-http://localhost:4005}"
|
|
MCP_ORIGIN="${MCP_ORIGIN:-http://localhost:4007}"
|
|
JWT_SECRET="${JWT_SECRET:-dev-secret-do-not-use-in-prod}"
|
|
PRODUCT_ID="${PRODUCT_ID:-notelett}"
|
|
SMOKE_USER_ID="${SMOKE_USER_ID:-notelett-smoke-user}"
|
|
SMOKE_START_BACKEND="${SMOKE_START_BACKEND:-1}"
|
|
SMOKE_SKIP_PLATFORM="${SMOKE_SKIP_PLATFORM:-0}"
|
|
|
|
BACKEND_PID=""
|
|
BACKEND_LOG="${TMPDIR:-/tmp}/notelett-local-smoke-backend.log"
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: scripts/local-smoke.sh [--no-start] [--skip-platform] [--help]
|
|
|
|
Checks:
|
|
- NoteLett GET /health
|
|
- NoteLett GET /api/bootstrap
|
|
- platform-service, extraction-service, and mcp-server /health
|
|
- authenticated workspace + note create/read/delete flow in DB_PROVIDER=memory
|
|
|
|
Environment:
|
|
NOTELETT_URL=http://localhost:4016
|
|
PLATFORM_URL=http://localhost:4003
|
|
EXTRACTION_URL=http://localhost:4005
|
|
MCP_ORIGIN=http://localhost:4007
|
|
JWT_SECRET=dev-secret-do-not-use-in-prod
|
|
SMOKE_START_BACKEND=1
|
|
SMOKE_SKIP_PLATFORM=0
|
|
COMMON_PLAT_DIR=../learning_ai/learning_ai_common_plat
|
|
USAGE
|
|
}
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--)
|
|
;;
|
|
--no-start)
|
|
SMOKE_START_BACKEND=0
|
|
;;
|
|
--skip-platform)
|
|
SMOKE_SKIP_PLATFORM=1
|
|
;;
|
|
--help|-h)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $arg" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
cleanup() {
|
|
if [[ -n "$BACKEND_PID" ]] && kill -0 "$BACKEND_PID" >/dev/null 2>&1; then
|
|
kill "$BACKEND_PID" >/dev/null 2>&1 || true
|
|
wait "$BACKEND_PID" >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
curl_json() {
|
|
local method="$1"
|
|
local url="$2"
|
|
local body="${3:-}"
|
|
shift 3 || true
|
|
|
|
if [[ -n "$body" ]]; then
|
|
curl -fsS -X "$method" "$url" \
|
|
-H 'content-type: application/json' \
|
|
"$@" \
|
|
--data "$body"
|
|
else
|
|
curl -fsS -X "$method" "$url" "$@"
|
|
fi
|
|
}
|
|
|
|
healthy() {
|
|
curl -fsS "$NOTELETT_URL/health" >/dev/null 2>&1
|
|
}
|
|
|
|
ensure_common_platform_builds() {
|
|
if [[ ! -d "$COMMON_PLAT_DIR" ]]; then
|
|
echo "error: missing common-platform checkout at $COMMON_PLAT_DIR" >&2
|
|
return 1
|
|
fi
|
|
|
|
local palace_dist="$COMMON_PLAT_DIR/packages/palace/dist/index.js"
|
|
if [[ ! -f "$palace_dist" ]]; then
|
|
echo "info: building linked common-platform @bytelyst/palace package"
|
|
GITEA_NPM_TOKEN="${GITEA_NPM_TOKEN:-dummy}" \
|
|
pnpm -C "$COMMON_PLAT_DIR" --filter @bytelyst/palace run build
|
|
fi
|
|
|
|
local llm_dist="$COMMON_PLAT_DIR/packages/llm/dist/index.js"
|
|
if [[ ! -f "$llm_dist" ]] || ! grep -q 'buildVisionMessage' "$llm_dist"; then
|
|
echo "info: building linked common-platform @bytelyst/llm package"
|
|
GITEA_NPM_TOKEN="${GITEA_NPM_TOKEN:-dummy}" \
|
|
pnpm -C "$COMMON_PLAT_DIR" --filter @bytelyst/llm run build
|
|
fi
|
|
}
|
|
|
|
start_backend_if_needed() {
|
|
if healthy; then
|
|
echo "ok: NoteLett backend already healthy at $NOTELETT_URL"
|
|
return
|
|
fi
|
|
|
|
if [[ "$SMOKE_START_BACKEND" != "1" ]]; then
|
|
echo "error: NoteLett backend is not healthy at $NOTELETT_URL and SMOKE_START_BACKEND=0" >&2
|
|
return 1
|
|
fi
|
|
|
|
echo "info: starting NoteLett backend in memory mode"
|
|
ensure_common_platform_builds
|
|
(
|
|
cd "$ROOT_DIR"
|
|
DB_PROVIDER=memory \
|
|
NODE_ENV=development \
|
|
JWT_SECRET="$JWT_SECRET" \
|
|
PRODUCT_ID="$PRODUCT_ID" \
|
|
pnpm --filter @notelett/backend run dev
|
|
) >"$BACKEND_LOG" 2>&1 &
|
|
BACKEND_PID="$!"
|
|
|
|
for _ in {1..60}; do
|
|
if healthy; then
|
|
echo "ok: NoteLett backend started at $NOTELETT_URL"
|
|
return
|
|
fi
|
|
if ! kill -0 "$BACKEND_PID" >/dev/null 2>&1; then
|
|
echo "error: backend exited before becoming healthy; log follows" >&2
|
|
tail -n 80 "$BACKEND_LOG" >&2 || true
|
|
return 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
echo "error: backend did not become healthy within 60 seconds; log follows" >&2
|
|
tail -n 80 "$BACKEND_LOG" >&2 || true
|
|
return 1
|
|
}
|
|
|
|
generate_token() {
|
|
(
|
|
cd "$ROOT_DIR/backend"
|
|
JWT_SECRET="$JWT_SECRET" PRODUCT_ID="$PRODUCT_ID" SMOKE_USER_ID="$SMOKE_USER_ID" node --input-type=module <<'NODE'
|
|
import { SignJWT } from 'jose';
|
|
|
|
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
|
|
const productId = process.env.PRODUCT_ID ?? 'notelett';
|
|
const sub = process.env.SMOKE_USER_ID ?? 'notelett-smoke-user';
|
|
|
|
const token = await new SignJWT({
|
|
email: 'smoke@notelett.local',
|
|
role: 'admin',
|
|
productId,
|
|
type: 'access',
|
|
})
|
|
.setProtectedHeader({ alg: 'HS256' })
|
|
.setSubject(sub)
|
|
.setIssuer('bytelyst-platform')
|
|
.setIssuedAt()
|
|
.setExpirationTime('15m')
|
|
.sign(secret);
|
|
|
|
process.stdout.write(token);
|
|
NODE
|
|
)
|
|
}
|
|
|
|
json_field_equals() {
|
|
local json="$1"
|
|
local field="$2"
|
|
local expected="$3"
|
|
|
|
JSON_INPUT="$json" FIELD="$field" EXPECTED="$expected" node --input-type=module <<'NODE'
|
|
const body = JSON.parse(process.env.JSON_INPUT ?? '{}');
|
|
const actual = body[process.env.FIELD];
|
|
if (actual !== process.env.EXPECTED) {
|
|
console.error(`Expected ${process.env.FIELD}=${process.env.EXPECTED}, got ${String(actual)}`);
|
|
process.exit(1);
|
|
}
|
|
NODE
|
|
}
|
|
|
|
check_platform_dependencies() {
|
|
if [[ "$SMOKE_SKIP_PLATFORM" == "1" ]]; then
|
|
echo "skip: platform dependency checks disabled"
|
|
return
|
|
fi
|
|
|
|
curl -fsS "$PLATFORM_URL/health" >/dev/null
|
|
echo "ok: platform-service health"
|
|
|
|
curl -fsS "$EXTRACTION_URL/health" >/dev/null
|
|
echo "ok: extraction-service health"
|
|
|
|
curl -fsS "$MCP_ORIGIN/health" >/dev/null
|
|
echo "ok: mcp-server health"
|
|
}
|
|
|
|
run_product_flow() {
|
|
local token="$1"
|
|
local run_id
|
|
run_id="smoke-$(date +%s)-$$"
|
|
local workspace_id="ws-$run_id"
|
|
local note_id="note-$run_id"
|
|
local auth_header="authorization: Bearer $token"
|
|
|
|
local workspace_body
|
|
workspace_body="{\"id\":\"$workspace_id\",\"name\":\"Smoke Workspace\",\"members\":[]}"
|
|
local workspace
|
|
workspace="$(curl_json POST "$NOTELETT_API_URL/workspaces" "$workspace_body" -H "$auth_header")"
|
|
json_field_equals "$workspace" id "$workspace_id"
|
|
echo "ok: authenticated workspace create"
|
|
|
|
local note_body
|
|
note_body="{\"id\":\"$note_id\",\"workspaceId\":\"$workspace_id\",\"title\":\"Smoke Note\",\"body\":\"<p>Local smoke note body.</p>\",\"tags\":[\"smoke\"],\"links\":[]}"
|
|
local note
|
|
note="$(curl_json POST "$NOTELETT_API_URL/notes" "$note_body" -H "$auth_header")"
|
|
json_field_equals "$note" id "$note_id"
|
|
echo "ok: authenticated note create"
|
|
|
|
local fetched
|
|
fetched="$(curl_json GET "$NOTELETT_API_URL/notes/$note_id?workspaceId=$workspace_id" "" -H "$auth_header")"
|
|
json_field_equals "$fetched" productId "$PRODUCT_ID"
|
|
json_field_equals "$fetched" id "$note_id"
|
|
echo "ok: authenticated note read"
|
|
|
|
curl_json DELETE "$NOTELETT_API_URL/notes/$note_id?workspaceId=$workspace_id" "" -H "$auth_header" >/dev/null 2>&1 || true
|
|
curl_json DELETE "$NOTELETT_API_URL/workspaces/$workspace_id" "" -H "$auth_header" >/dev/null 2>&1 || true
|
|
echo "ok: smoke cleanup attempted"
|
|
}
|
|
|
|
main() {
|
|
start_backend_if_needed
|
|
|
|
curl -fsS "$NOTELETT_URL/health" >/dev/null
|
|
echo "ok: NoteLett health"
|
|
|
|
local bootstrap
|
|
bootstrap="$(curl_json GET "$NOTELETT_API_URL/bootstrap" "")"
|
|
json_field_equals "$bootstrap" productId "$PRODUCT_ID"
|
|
echo "ok: NoteLett bootstrap"
|
|
|
|
check_platform_dependencies
|
|
|
|
local token
|
|
token="$(generate_token)"
|
|
run_product_flow "$token"
|
|
|
|
echo "ok: local production-readiness smoke passed"
|
|
}
|
|
|
|
main
|