# Makefile for Bifrost # Variables HOST ?= localhost PORT ?= 8080 APP_DIR ?= PROMETHEUS_LABELS ?= LOG_STYLE ?= json LOG_LEVEL ?= info TEST_REPORTS_DIR ?= test-reports GOTESTSUM_FORMAT ?= standard-verbose FLOW ?= VERSION ?= dev-build LOCAL ?= DEBUG ?= # Colors for output RED=\033[0;31m GREEN=\033[0;32m YELLOW=\033[1;33m BLUE=\033[0;34m CYAN=\033[0;36m NC=\033[0m # No Color ECHO := printf '%b\n' # nvm requires bash-compatible shell semantics; /bin/sh is dash on some Linux distros. SHELL := /bin/bash # Ensures the Node version pinned in .nvmrc is active before any npm/node call. # nvm is a shell function, so each recipe that needs it must inline this snippet # via `$(USE_NODE); `. USE_NODE = NVM_SH="$${NVM_DIR:-$$HOME/.nvm}/nvm.sh"; \ [ -s "$$NVM_SH" ] || NVM_SH="$$(brew --prefix nvm 2>/dev/null)/nvm.sh"; \ if [ -s "$$NVM_SH" ]; then . "$$NVM_SH" >/dev/null && nvm install >/dev/null 2>&1 && nvm use >/dev/null 2>&1; fi .PHONY: all help dev dev-pulse build-ui build build-cli run run-cli install-air install-pulse clean test test-cli install-ui setup-workspace work-init work-clean docs docker-image docker-run cleanup-enterprise mod-tidy test-integrations-py test-integrations-ts install-playwright run-e2e run-e2e-ui run-e2e-headed format ui all: help # Include deployment recipes include recipes/fly.mk include recipes/ecs.mk include recipes/local-k8s.mk # Default target help: ## Show this help message @$(ECHO) "$(BLUE)Bifrost Development - Available Commands:$(NC)" @$(ECHO) "" @grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(GREEN)%-15s$(NC) %s\n", $$1, $$2}' @$(ECHO) "" @$(ECHO) "$(YELLOW)Environment Variables:$(NC)" @$(ECHO) " HOST Server host (default: localhost)" @$(ECHO) " PORT Server port (default: 8080)" @$(ECHO) " PROMETHEUS_LABELS Labels for Prometheus metrics" @$(ECHO) " LOG_STYLE Logger output format: json|pretty (default: json)" @$(ECHO) " LOG_LEVEL Logger level: debug|info|warn|error (default: info)" @$(ECHO) " APP_DIR App data directory inside container (default: /app/data)" @$(ECHO) " LOCAL Use local go.work for builds (e.g., make build LOCAL=1)" @$(ECHO) " DEBUG Enable delve debugger on port 2345 (e.g., make dev DEBUG=1, make test-core DEBUG=1, make test-governance DEBUG=1)" @$(ECHO) "" @$(ECHO) "$(YELLOW)Test Configuration:$(NC)" @$(ECHO) " TEST_REPORTS_DIR Directory for HTML test reports (default: test-reports)" @$(ECHO) " GOTESTSUM_FORMAT Test output format: testname|dots|pkgname|standard-verbose (default: standard-verbose)" @$(ECHO) " TESTCASE Exact test name to run (e.g., TestVirtualKeyTokenRateLimit)" @$(ECHO) " PATTERN Substring pattern to filter tests (alternative to TESTCASE)" @$(ECHO) " FLOW E2E test flow to run: providers|virtual-keys (default: all)" cleanup-enterprise: ## Clean up enterprise directories if present @$(ECHO) "$(GREEN)Cleaning up enterprise...$(NC)" @if [ -d "ui/app/enterprise" ]; then rm -rf ui/app/enterprise; fi @$(ECHO) "$(GREEN)Enterprise cleaned up$(NC)" install-ui: cleanup-enterprise @$(USE_NODE); \ which node > /dev/null || ($(ECHO) "$(RED)Error: Node.js is not installed. Please install Node.js first.$(NC)" && exit 1); \ which npm > /dev/null || ($(ECHO) "$(RED)Error: npm is not installed. Please install npm first.$(NC)" && exit 1); \ $(ECHO) "$(GREEN)Node.js $$(node -v) and npm $$(npm -v) are installed$(NC)"; \ if [ ! -d "ui/node_modules" ] || [ "ui/package.json" -nt "ui/node_modules/.package-lock.json" ] || [ "ui/package-lock.json" -nt "ui/node_modules/.package-lock.json" ]; then \ $(ECHO) "$(YELLOW)Dependencies changed, running npm ci...$(NC)"; \ cd ui && npm ci; \ else \ $(ECHO) "$(GREEN)UI dependencies up to date, skipping install$(NC)"; \ fi @$(ECHO) "$(GREEN)UI deps are in sync$(NC)" install-air: ## Install air for hot reloading (if not already installed) @which air > /dev/null || ($(ECHO) "$(YELLOW)Installing air for hot reloading...$(NC)" && go install github.com/air-verse/air@latest) @$(ECHO) "$(GREEN)Air is ready$(NC)" install-pulse: ## Install pulse for hot reloading (if not already installed) @which pulse > /dev/null || ($(ECHO) "$(YELLOW)Installing pulse for hot reloading...$(NC)" && go install github.com/Pratham-Mishra04/pulse@latest) @$(ECHO) "$(GREEN)Pulse is ready$(NC)" install-delve: ## Install delve for debugging (if not already installed) @which dlv > /dev/null || ($(ECHO) "$(YELLOW)Installing delve for debugging...$(NC)" && go install github.com/go-delve/delve/cmd/dlv@latest) @$(ECHO) "$(GREEN)Delve is ready$(NC)" install-gotestsum: ## Install gotestsum for test reporting (if not already installed) @which gotestsum > /dev/null || ($(ECHO) "$(YELLOW)Installing gotestsum for test reporting...$(NC)" && go install gotest.tools/gotestsum@latest) @$(ECHO) "$(GREEN)gotestsum is ready$(NC)" install-junit-viewer: ## Install junit-viewer for HTML report generation (if not already installed) @if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(GREEN)junit-viewer is already installed$(NC)"; \ else \ $(ECHO) "$(YELLOW)Installing junit-viewer for HTML reports...$(NC)"; \ $(USE_NODE); \ if npm install -g junit-viewer 2>&1; then \ $(ECHO) "$(GREEN)junit-viewer installed successfully$(NC)"; \ else \ $(ECHO) "$(RED)Failed to install junit-viewer. HTML reports will be skipped.$(NC)"; \ $(ECHO) "$(YELLOW)You can install it manually: npm install -g junit-viewer$(NC)"; \ exit 0; \ fi; \ fi; \ else \ $(ECHO) "$(YELLOW)CI environment detected, skipping junit-viewer installation$(NC)"; \ fi dev: install-ui install-air setup-workspace $(if $(DEBUG),install-delve) ## Start complete development environment (UI + API with proxy) @$(ECHO) "$(GREEN)Starting Bifrost complete development environment...$(NC)" @$(ECHO) "$(YELLOW)This will start:$(NC)" @$(ECHO) " 1. UI development server (localhost:3000)" @$(ECHO) " 2. API server with UI proxy (localhost:$(PORT))" @$(ECHO) "$(CYAN)Access everything at: http://localhost:$(PORT)$(NC)" @if [ -n "$(DEBUG)" ]; then \ $(ECHO) "$(CYAN) 3. Debugger (delve) listening on port 2345$(NC)"; \ fi @if [ ! -d "transports/bifrost-http/ui" ]; then \ $(ECHO) "$(YELLOW)Creating transports/bifrost-http/ui directory...$(NC)"; \ mkdir -p transports/bifrost-http/ui; \ touch transports/bifrost-http/ui/.tmp; \ fi @$(ECHO) "" @$(ECHO) "$(YELLOW)Starting UI development server...$(NC)" @$(USE_NODE); if [ -n "$(DISABLE_PROFILER)" ]; then \ $(ECHO) "$(CYAN)DevProfiler disabled for testing$(NC)"; \ cd ui && BIFROST_DISABLE_PROFILER=1 npm run dev & \ else \ cd ui && npm run dev & \ fi @sleep 3 @$(ECHO) "$(YELLOW)Starting API server with UI proxy...$(NC)" @$(MAKE) setup-workspace >/dev/null @if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ if [ -n "$(DEBUG)" ]; then \ $(ECHO) "$(CYAN)Starting with air + delve debugger on port 2345...$(NC)"; \ $(ECHO) "$(YELLOW)Attach your debugger to localhost:2345$(NC)"; \ cd transports/bifrost-http && BIFROST_UI_DEV=true air -c .air.debug.toml -- \ -host "$(HOST)" \ -port "$(PORT)" \ -log-style "$(LOG_STYLE)" \ -log-level "$(LOG_LEVEL)" \ $(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") \ $(if $(APP_DIR),-app-dir "$(APP_DIR)"); \ else \ cd transports/bifrost-http && BIFROST_UI_DEV=true air -c .air.toml -- \ -host "$(HOST)" \ -port "$(PORT)" \ -log-style "$(LOG_STYLE)" \ -log-level "$(LOG_LEVEL)" \ $(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") \ $(if $(APP_DIR),-app-dir "$(APP_DIR)"); \ fi dev-pulse: install-ui install-pulse setup-workspace $(if $(DEBUG),install-delve) ## Start complete development environment using pulse for hot reloading @$(ECHO) "$(GREEN)Starting Bifrost complete development environment (pulse)...$(NC)" @$(ECHO) "$(YELLOW)This will start:$(NC)" @$(ECHO) " 1. UI development server (localhost:3000)" @$(ECHO) " 2. API server with UI proxy (localhost:$(PORT))" @$(ECHO) "$(CYAN)Access everything at: http://localhost:$(PORT)$(NC)" @if [ -n "$(DEBUG)" ]; then \ $(ECHO) "$(CYAN) 3. Debugger (delve) listening on port 2345$(NC)"; \ fi @if [ ! -d "transports/bifrost-http/ui" ]; then \ $(ECHO) "$(YELLOW)Creating transports/bifrost-http/ui directory...$(NC)"; \ mkdir -p transports/bifrost-http/ui; \ touch transports/bifrost-http/ui/.tmp; \ fi @$(ECHO) "" @$(ECHO) "$(YELLOW)Starting UI development server...$(NC)" @$(USE_NODE); if [ -n "$(DISABLE_PROFILER)" ]; then \ $(ECHO) "$(CYAN)DevProfiler disabled for testing$(NC)"; \ cd ui && BIFROST_DISABLE_PROFILER=1 npm run dev & \ else \ cd ui && npm run dev & \ fi @sleep 3 @$(ECHO) "$(YELLOW)Starting API server with UI proxy...$(NC)" @$(MAKE) setup-workspace >/dev/null @if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ if [ -n "$(DEBUG)" ]; then \ $(ECHO) "$(CYAN)Starting with pulse + delve debugger on port 2345...$(NC)"; \ $(ECHO) "$(YELLOW)Attach your debugger to localhost:2345$(NC)"; \ BIFROST_UI_DEV=true pulse -- \ -host "$(HOST)" \ -port "$(PORT)" \ -log-style "$(LOG_STYLE)" \ -log-level "$(LOG_LEVEL)" \ $(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") \ $(if $(APP_DIR),-app-dir "$(APP_DIR)"); \ else \ BIFROST_UI_DEV=true pulse -- \ -host "$(HOST)" \ -port "$(PORT)" \ -log-style "$(LOG_STYLE)" \ -log-level "$(LOG_LEVEL)" \ $(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") \ $(if $(APP_DIR),-app-dir "$(APP_DIR)"); \ fi build-ui: install-ui ## Build ui @$(ECHO) "$(GREEN)Building ui...$(NC)" @rm -rf ui/.next @$(USE_NODE); cd ui && npm run build && npm run copy-build build: build-ui ## Build bifrost-http binary @if [ -n "$(LOCAL)" ]; then \ $(ECHO) "$(GREEN)╔═══════════════════════════════════════════════╗$(NC)"; \ $(ECHO) "$(GREEN)║ Building bifrost-http with local go.work... ║$(NC)"; \ $(ECHO) "$(GREEN)╚═══════════════════════════════════════════════╝$(NC)"; \ else \ $(ECHO) "$(GREEN)╔═══════════════════════════════════════╗$(NC)"; \ $(ECHO) "$(GREEN)║ Building bifrost-http... ║$(NC)"; \ $(ECHO) "$(GREEN)╚═══════════════════════════════════════╝$(NC)"; \ fi @if [ -n "$(DYNAMIC)" ]; then \ $(ECHO) "$(YELLOW)Note: This will create a dynamically linked build.$(NC)"; \ else \ $(ECHO) "$(YELLOW)Note: This will create a statically linked build.$(NC)"; \ fi @mkdir -p ./tmp @TARGET_OS="$(GOOS)"; \ TARGET_ARCH="$(GOARCH)"; \ ACTUAL_OS=$$(uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/darwin/;s/linux/linux/;s/mingw.*/windows/'); \ ACTUAL_ARCH=$$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/;s/arm64/arm64/'); \ if [ -z "$$TARGET_OS" ]; then \ TARGET_OS=$$ACTUAL_OS; \ fi; \ if [ -z "$$TARGET_ARCH" ]; then \ TARGET_ARCH=$$ACTUAL_ARCH; \ fi; \ HOST_OS=$$ACTUAL_OS; \ HOST_ARCH=$$ACTUAL_ARCH; \ $(ECHO) "$(CYAN)Host: $$HOST_OS/$$HOST_ARCH | Target: $$TARGET_OS/$$TARGET_ARCH$(NC)"; \ if [ "$$TARGET_OS" = "linux" ] && [ "$$HOST_OS" = "linux" ]; then \ if [ -n "$(DYNAMIC)" ]; then \ $(ECHO) "$(CYAN)Building for $$TARGET_OS/$$TARGET_ARCH with dynamic linking...$(NC)"; \ cd transports/bifrost-http && CGO_ENABLED=1 GOOS=$$TARGET_OS GOARCH=$$TARGET_ARCH $(if $(LOCAL),,GOWORK=off) go build \ -ldflags="-w -s -X main.Version=v$(VERSION)" \ -a -trimpath \ -o ../../tmp/bifrost-http \ .; \ else \ $(ECHO) "$(CYAN)Building for $$TARGET_OS/$$TARGET_ARCH with static linking...$(NC)"; \ cd transports/bifrost-http && CGO_ENABLED=1 GOOS=$$TARGET_OS GOARCH=$$TARGET_ARCH $(if $(LOCAL),,GOWORK=off) go build \ -ldflags="-w -s -extldflags "-static" -X main.Version=v$(VERSION)" \ -a -trimpath \ -tags "sqlite_static" \ -o ../../tmp/bifrost-http \ .; \ fi; \ $(ECHO) "$(GREEN)Built: tmp/bifrost-http (version: v$(VERSION))$(NC)"; \ elif [ "$$TARGET_OS" = "$$HOST_OS" ] && [ "$$TARGET_ARCH" = "$$HOST_ARCH" ]; then \ $(ECHO) "$(CYAN)Building for $$TARGET_OS/$$TARGET_ARCH (native build with CGO)...$(NC)"; \ cd transports/bifrost-http && CGO_ENABLED=1 GOOS=$$TARGET_OS GOARCH=$$TARGET_ARCH $(if $(LOCAL),,GOWORK=off) go build \ -ldflags="-w -s -X main.Version=v$(VERSION)" \ -a -trimpath \ -tags "sqlite_static" \ -o ../../tmp/bifrost-http \ .; \ $(ECHO) "$(GREEN)Built: tmp/bifrost-http (version: v$(VERSION))$(NC)"; \ else \ $(ECHO) "$(YELLOW)Cross-compilation detected: $$HOST_OS/$$HOST_ARCH -> $$TARGET_OS/$$TARGET_ARCH$(NC)"; \ $(ECHO) "$(CYAN)Using Docker for cross-compilation...$(NC)"; \ $(MAKE) _build-with-docker TARGET_OS=$$TARGET_OS TARGET_ARCH=$$TARGET_ARCH $(if $(DYNAMIC),DYNAMIC=$(DYNAMIC)); \ fi build-cli: ## Build bifrost CLI binary @$(ECHO) "$(GREEN)Building bifrost CLI...$(NC)" @mkdir -p ./tmp @cd cli && $(if $(LOCAL),,GOWORK=off) go build -ldflags "-X main.version=v0.1.1-dev" -o ../tmp/bifrost . @$(ECHO) "$(GREEN)Built: tmp/bifrost$(NC)" _build-with-docker: # Internal target for Docker-based cross-compilation @$(ECHO) "$(CYAN)Using Docker for cross-compilation...$(NC)"; \ if [ "$(TARGET_OS)" = "linux" ]; then \ if [ -n "$(DYNAMIC)" ]; then \ $(ECHO) "$(CYAN)Building for $(TARGET_OS)/$(TARGET_ARCH) in Docker container with dynamic linking...$(NC)"; \ docker run --rm \ --platform linux/$(TARGET_ARCH) \ -v "$(shell pwd):/workspace" \ -w /workspace/transports/bifrost-http \ -e CGO_ENABLED=1 \ -e GOOS=$(TARGET_OS) \ -e GOARCH=$(TARGET_ARCH) \ $(if $(LOCAL),,-e GOWORK=off) \ golang:1.26.1-alpine3.23 \ sh -c "apk add --no-cache gcc musl-dev && \ go build \ -ldflags='-w -s -X main.Version=v$(VERSION)' \ -a -trimpath \ -o ../../tmp/bifrost-http \ ."; \ else \ $(ECHO) "$(CYAN)Building for $(TARGET_OS)/$(TARGET_ARCH) in Docker container...$(NC)"; \ docker run --rm \ --platform linux/$(TARGET_ARCH) \ -v "$(shell pwd):/workspace" \ -w /workspace/transports/bifrost-http \ -e CGO_ENABLED=1 \ -e GOOS=$(TARGET_OS) \ -e GOARCH=$(TARGET_ARCH) \ $(if $(LOCAL),,-e GOWORK=off) \ golang:1.26.1-alpine3.23 \ sh -c "apk add --no-cache gcc musl-dev && \ go build \ -ldflags='-w -s -extldflags "-static" -X main.Version=v$(VERSION)' \ -a -trimpath \ -tags sqlite_static \ -o ../../tmp/bifrost-http \ ."; \ fi; \ $(ECHO) "$(GREEN)Built: tmp/bifrost-http ($(TARGET_OS)/$(TARGET_ARCH), version: v$(VERSION))$(NC)"; \ else \ $(ECHO) "$(RED)Error: Docker cross-compilation only supports Linux targets$(NC)"; \ $(ECHO) "$(YELLOW)For $(TARGET_OS), please build on a native $(TARGET_OS) machine$(NC)"; \ exit 1; \ fi docker-image: build-ui ## Build Docker image (LOCAL=1 to use Dockerfile.local) @$(ECHO) "$(GREEN)Building Docker image...$(NC)" $(eval GIT_SHA=$(shell git rev-parse --short HEAD)) $(eval DOCKERFILE=$(if $(LOCAL),transports/Dockerfile.local,transports/Dockerfile)) @docker build -f $(DOCKERFILE) -t bifrost -t bifrost:$(GIT_SHA) -t bifrost:latest . @$(ECHO) "$(GREEN)Docker image built: bifrost, bifrost:$(GIT_SHA), bifrost:latest (using $(DOCKERFILE))$(NC)" docker-run: ## Run Docker container (Usage: make docker-run [CONFIG=path/to/config.json or path/to/dir/]) @$(ECHO) "$(GREEN)Running Docker container...$(NC)" @CONFIG_PATH="$(abspath $(CONFIG))"; \ if [ -n "$(CONFIG)" ]; then \ if [ -d "$$CONFIG_PATH" ]; then \ CONFIG_PATH="$$CONFIG_PATH/config.json"; \ fi; \ CONFIG_MOUNT="-v $$CONFIG_PATH:/app/data/config.json"; \ else \ CONFIG_MOUNT=""; \ fi; \ docker run -e APP_PORT=$(PORT) -e APP_HOST=0.0.0.0 -p $(PORT):$(PORT) -e LOG_LEVEL=$(LOG_LEVEL) -e LOG_STYLE=$(LOG_STYLE) -v $(shell pwd):/app/data $$CONFIG_MOUNT bifrost docs: ## Prepare local docs @$(ECHO) "$(GREEN)Preparing local docs...$(NC)" @cd docs && npx --yes mintlify@latest dev run: build ## Build and run bifrost-http (no hot reload) @$(ECHO) "$(GREEN)Running bifrost-http...$(NC)" @./tmp/bifrost-http \ -host "$(HOST)" \ -port "$(PORT)" \ -log-style "$(LOG_STYLE)" \ -log-level "$(LOG_LEVEL)" \ $(if $(PROMETHEUS_LABELS),-prometheus-labels "$(PROMETHEUS_LABELS)") $(if $(APP_DIR),-app-dir "$(APP_DIR)") run-cli: build-cli ## Run bifrost CLI (Usage: make run-cli [ARGS="--config ~/.bifrost/config.json"]) @$(ECHO) "$(GREEN)Running bifrost CLI...$(NC)" @./tmp/bifrost $(ARGS) clean: ## Clean build artifacts and temporary files @$(ECHO) "$(YELLOW)Cleaning build artifacts...$(NC)" @rm -rf tmp/ @rm -f transports/bifrost-http/build-errors.log @rm -rf transports/bifrost-http/tmp/ @rm -rf $(TEST_REPORTS_DIR)/ @$(ECHO) "$(GREEN)Clean complete$(NC)" clean-test-reports: ## Clean test reports only @$(ECHO) "$(YELLOW)Cleaning test reports...$(NC)" @rm -rf $(TEST_REPORTS_DIR)/ @$(ECHO) "$(GREEN)Test reports cleaned$(NC)" generate-html-reports: ## Convert existing XML reports to HTML @if ! which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(RED)Error: junit-viewer not installed$(NC)"; \ $(ECHO) "$(YELLOW)Install with: make install-junit-viewer$(NC)"; \ exit 1; \ fi @$(ECHO) "$(GREEN)Converting XML reports to HTML...$(NC)" @if [ ! -d "$(TEST_REPORTS_DIR)" ] || [ -z "$$(ls -A $(TEST_REPORTS_DIR)/*.xml 2>/dev/null)" ]; then \ $(ECHO) "$(YELLOW)No XML reports found in $(TEST_REPORTS_DIR)$(NC)"; \ $(ECHO) "$(YELLOW)Run tests first: make test-all$(NC)"; \ exit 0; \ fi @for xml in $(TEST_REPORTS_DIR)/*.xml; do \ html=$${xml%.xml}.html; \ $(ECHO) " Converting $$(basename $$xml) → $$(basename $$html)"; \ junit-viewer --results=$$xml --save=$$html 2>/dev/null || true; \ done @$(ECHO) "" @$(ECHO) "$(GREEN)✓ HTML reports generated$(NC)" @$(ECHO) "$(CYAN)View reports:$(NC)" @ls -1 $(TEST_REPORTS_DIR)/*.html 2>/dev/null | sed 's|$(TEST_REPORTS_DIR)/| open $(TEST_REPORTS_DIR)/|' || true test: install-gotestsum ## Run tests for bifrost-http @$(ECHO) "$(GREEN)Running bifrost-http tests...$(NC)" @mkdir -p $(TEST_REPORTS_DIR) @cd transports/bifrost-http && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../$(TEST_REPORTS_DIR)/bifrost-http.xml \ -- -v ./... @if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \ if junit-viewer --results=$(TEST_REPORTS_DIR)/bifrost-http.xml --save=$(TEST_REPORTS_DIR)/bifrost-http.html 2>/dev/null; then \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML report: $(TEST_REPORTS_DIR)/bifrost-http.html$(NC)"; \ $(ECHO) "$(CYAN)Open with: open $(TEST_REPORTS_DIR)/bifrost-http.html$(NC)"; \ else \ $(ECHO) "$(YELLOW)HTML generation failed. JUnit XML report available.$(NC)"; \ $(ECHO) "$(CYAN)JUnit XML report: $(TEST_REPORTS_DIR)/bifrost-http.xml$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(YELLOW)junit-viewer not installed. Install with: make install-junit-viewer$(NC)"; \ $(ECHO) "$(CYAN)JUnit XML report: $(TEST_REPORTS_DIR)/bifrost-http.xml$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $(TEST_REPORTS_DIR)/bifrost-http.xml$(NC)"; \ fi test-core: install-gotestsum $(if $(DEBUG),install-delve) ## Run core tests (Usage: make test-core PROVIDER=openai TESTCASE=TestName or PATTERN=substring, DEBUG=1 for debugger) @$(ECHO) "$(GREEN)Running core tests...$(NC)" @mkdir -p $(TEST_REPORTS_DIR) @if [ -n "$(PATTERN)" ] && [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(RED)Error: PATTERN and TESTCASE are mutually exclusive$(NC)"; \ $(ECHO) "$(YELLOW)Use PATTERN for substring matching or TESTCASE for exact match$(NC)"; \ exit 1; \ fi @TEST_FAILED=0; \ REPORT_FILE=""; \ if [ -n "$(PROVIDER)" ]; then \ $(ECHO) "$(CYAN)Running tests for provider: $(PROVIDER)$(NC)"; \ if [ ! -f "core/providers/$(PROVIDER)/$(PROVIDER)_test.go" ]; then \ $(ECHO) "$(RED)Error: Provider test file '$(PROVIDER)_test.go' not found in core/providers/$(PROVIDER)/$(NC)"; \ $(ECHO) "$(YELLOW)Available providers:$(NC)"; \ find core/providers -name "*_test.go" -type f 2>/dev/null | sed 's|core/providers/\([^/]*\)/.*|\1|' | sort -u | sed 's/^/ - /'; \ exit 1; \ fi; \ fi; \ if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ if [ -n "$(DEBUG)" ]; then \ $(ECHO) "$(CYAN)Debug mode enabled - delve debugger will listen on port 2345$(NC)"; \ $(ECHO) "$(YELLOW)Attach your debugger to localhost:2345$(NC)"; \ fi; \ if [ -n "$(PROVIDER)" ]; then \ PROVIDER_TEST_NAME=$$($(ECHO) "$(PROVIDER)" | awk '{print toupper(substr($$0,1,1)) tolower(substr($$0,2))}' | sed 's/openai/OpenAI/i; s/sgl/SGL/i'); \ if [ -n "$(TESTCASE)" ]; then \ CLEAN_TESTCASE="$(TESTCASE)"; \ CLEAN_TESTCASE=$${CLEAN_TESTCASE#Test$${PROVIDER_TEST_NAME}/}; \ CLEAN_TESTCASE=$${CLEAN_TESTCASE#$${PROVIDER_TEST_NAME}Tests/}; \ CLEAN_TESTCASE=$$($(ECHO) "$$CLEAN_TESTCASE" | sed 's|^Test[A-Z][A-Za-z]*/[A-Z][A-Za-z]*Tests/||'); \ $(ECHO) "$(CYAN)Running Test$${PROVIDER_TEST_NAME}/$${PROVIDER_TEST_NAME}Tests/$$CLEAN_TESTCASE...$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/core-$(PROVIDER)-$$(echo $$CLEAN_TESTCASE | sed 's|/|_|g').xml"; \ if [ -n "$(DEBUG)" ]; then \ cd core/providers/$(PROVIDER) && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v -test.run "^Test$${PROVIDER_TEST_NAME}$$/.*Tests/$$CLEAN_TESTCASE$$" || TEST_FAILED=1; \ else \ cd core/providers/$(PROVIDER) && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../../$$REPORT_FILE \ -- -v -timeout 20m -run "^Test$${PROVIDER_TEST_NAME}$$/.*Tests/$$CLEAN_TESTCASE$$" || TEST_FAILED=1; \ fi; \ cd ../../..; \ $(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \ junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \ $(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ elif [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running tests matching '$(PATTERN)' for $${PROVIDER_TEST_NAME}...$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/core-$(PROVIDER)-$(PATTERN).xml"; \ if [ -n "$(DEBUG)" ]; then \ cd core/providers/$(PROVIDER) && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v -test.run ".*$(PATTERN).*" || TEST_FAILED=1; \ else \ cd core/providers/$(PROVIDER) && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../../$$REPORT_FILE \ -- -v -timeout 20m -run ".*$(PATTERN).*" || TEST_FAILED=1; \ fi; \ cd ../../..; \ $(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \ junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \ $(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) "$(CYAN)Running Test$${PROVIDER_TEST_NAME}...$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/core-$(PROVIDER).xml"; \ if [ -n "$(DEBUG)" ]; then \ cd core/providers/$(PROVIDER) && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v -test.run "^Test$${PROVIDER_TEST_NAME}$$" || TEST_FAILED=1; \ else \ cd core/providers/$(PROVIDER) && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../../$$REPORT_FILE \ -- -v -timeout 20m -run "^Test$${PROVIDER_TEST_NAME}$$" || TEST_FAILED=1; \ fi; \ cd ../../..; \ $(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \ junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \ $(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ fi; \ else \ if [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(RED)Error: TESTCASE requires PROVIDER to be specified$(NC)"; \ $(ECHO) "$(YELLOW)Usage: make test-core PROVIDER=openai TESTCASE=SpeechSynthesisStreamAdvanced/MultipleVoices_Streaming/StreamingVoice_echo$(NC)"; \ exit 1; \ fi; \ if [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running tests matching '$(PATTERN)' across all providers...$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/core-all-$(PATTERN).xml"; \ if [ -n "$(DEBUG)" ]; then \ cd core && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 ./providers/... -- -test.v -test.run ".*$(PATTERN).*" || TEST_FAILED=1; \ else \ cd core && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../$$REPORT_FILE \ -- -v -timeout 20m -run ".*$(PATTERN).*" ./providers/... || TEST_FAILED=1; \ fi; \ else \ REPORT_FILE="$(TEST_REPORTS_DIR)/core-all.xml"; \ if [ -n "$(DEBUG)" ]; then \ cd core && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 ./providers/... -- -test.v || TEST_FAILED=1; \ else \ cd core && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../$$REPORT_FILE \ -- -v ./providers/... || TEST_FAILED=1; \ fi; \ fi; \ cd ..; \ $(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \ junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \ $(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ fi; \ if [ -f "$$REPORT_FILE" ]; then \ ALL_FAILED=$$(grep -B 1 '/dev/null | \ grep '/dev/null | \ grep ']*name="'"$$ESCAPED"'"[^>]*>.*?//gs' "$(REPORT_FILE).tmp" 2>/dev/null || true; \ fi; \ done; \ if [ -f "$(REPORT_FILE).tmp" ]; then \ mv "$(REPORT_FILE).tmp" "$(REPORT_FILE)"; \ fi; \ fi; \ fi test-plugins: install-gotestsum ## Run plugin tests @$(ECHO) "$(GREEN)Running plugin tests...$(NC)" @mkdir -p $(TEST_REPORTS_DIR) @if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ cd plugins && find . -name "*.go" -path "*/tests/*" -o -name "*_test.go" | head -1 > /dev/null && \ for dir in $$(find . -name "*_test.go" -exec dirname {} \; | sort -u); do \ plugin_name=$$(echo $$dir | sed 's|^\./||' | sed 's|/|-|g'); \ $(ECHO) "Testing $$dir..."; \ cd $$dir && gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../$(TEST_REPORTS_DIR)/plugin-$$plugin_name.xml \ -- -v ./... && cd - > /dev/null; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report for $$plugin_name...$(NC)"; \ junit-viewer --results=../$(TEST_REPORTS_DIR)/plugin-$$plugin_name.xml --save=../$(TEST_REPORTS_DIR)/plugin-$$plugin_name.html 2>/dev/null || true; \ fi; \ fi; \ done || $(ECHO) "No plugin tests found" @$(ECHO) "" @if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ $(ECHO) "$(CYAN)HTML reports saved to $(TEST_REPORTS_DIR)/plugin-*.html$(NC)"; \ else \ $(ECHO) "$(CYAN)JUnit XML reports saved to $(TEST_REPORTS_DIR)/plugin-*.xml$(NC)"; \ fi test-framework: install-gotestsum ## Run framework tests @$(ECHO) "$(GREEN)Running framework tests...$(NC)" @mkdir -p $(TEST_REPORTS_DIR) @if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ cd framework && find . -name "*.go" -path "*/tests/*" -o -name "*_test.go" | head -1 > /dev/null && \ for dir in $$(find . -name "*_test.go" -exec dirname {} \; | sort -u); do \ pkg_name=$$(echo $$dir | sed 's|^\./||' | sed 's|/|-|g'); \ $(ECHO) "Testing $$dir..."; \ cd $$dir && gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../$(TEST_REPORTS_DIR)/framework-$$pkg_name.xml \ -- -v ./... && cd - > /dev/null; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report for $$pkg_name...$(NC)"; \ junit-viewer --results=../$(TEST_REPORTS_DIR)/framework-$$pkg_name.xml --save=../$(TEST_REPORTS_DIR)/framework-$$pkg_name.html 2>/dev/null || true; \ fi; \ fi; \ done || $(ECHO) "No framework tests found" @$(ECHO) "" @if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ $(ECHO) "$(CYAN)HTML reports saved to $(TEST_REPORTS_DIR)/framework-*.html$(NC)"; \ else \ $(ECHO) "$(CYAN)JUnit XML reports saved to $(TEST_REPORTS_DIR)/framework-*.xml$(NC)"; \ fi test-http-transport: install-gotestsum ## Run HTTP transport tests @$(ECHO) "$(GREEN)Running HTTP transport tests...$(NC)" @mkdir -p $(TEST_REPORTS_DIR) @if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ cd transports/bifrost-http && find . -name "*.go" -path "*/tests/*" -o -name "*_test.go" | head -1 > /dev/null && \ for dir in $$(find . -name "*_test.go" -exec dirname {} \; | sort -u); do \ pkg_name=$$(echo $$dir | sed 's|^\./||' | sed 's|/|-|g'); \ $(ECHO) "Testing $$dir..."; \ cd $$dir && gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../../$(TEST_REPORTS_DIR)/http-transport-$$pkg_name.xml \ -- -v ./... && cd - > /dev/null; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report for $$pkg_name...$(NC)"; \ junit-viewer --results=../../$(TEST_REPORTS_DIR)/http-transport-$$pkg_name.xml --save=../../$(TEST_REPORTS_DIR)/http-transport-$$pkg_name.html 2>/dev/null || true; \ fi; \ fi; \ done || $(ECHO) "No HTTP transport tests found" @$(ECHO) "" @if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ $(ECHO) "$(CYAN)HTML reports saved to $(TEST_REPORTS_DIR)/http-transport-*.html$(NC)"; \ else \ $(ECHO) "$(CYAN)JUnit XML reports saved to $(TEST_REPORTS_DIR)/http-transport-*.xml$(NC)"; \ fi test-governance: install-gotestsum $(if $(DEBUG),install-delve) ## Run governance tests (Usage: make test-governance TESTCASE=TestName or PATTERN=substring, DEBUG=1 for debugger) @$(ECHO) "$(GREEN)Running governance tests...$(NC)" @mkdir -p $(TEST_REPORTS_DIR) @if [ -n "$(PATTERN)" ] && [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(RED)Error: PATTERN and TESTCASE are mutually exclusive$(NC)"; \ $(ECHO) "$(YELLOW)Use PATTERN for substring matching or TESTCASE for exact match$(NC)"; \ exit 1; \ fi @if [ ! -d "tests/governance" ]; then \ $(ECHO) "$(RED)Error: Governance tests directory not found$(NC)"; \ exit 1; \ fi @TEST_FAILED=0; \ REPORT_FILE=""; \ if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ if [ -n "$(DEBUG)" ]; then \ $(ECHO) "$(CYAN)Debug mode enabled - delve debugger will listen on port 2345$(NC)"; \ $(ECHO) "$(YELLOW)Attach your debugger to localhost:2345$(NC)"; \ fi; \ if [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(CYAN)Running test case: $(TESTCASE)$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/governance-$$(echo $(TESTCASE) | sed 's|/|_|g').xml"; \ if [ -n "$(DEBUG)" ]; then \ cd tests/governance && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v -test.run "^$(TESTCASE)$$" || TEST_FAILED=1; \ else \ cd tests/governance && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../$$REPORT_FILE \ -- -v -run "^$(TESTCASE)$$" || TEST_FAILED=1; \ fi; \ cd ../..; \ $(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \ junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \ $(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ elif [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running tests matching '$(PATTERN)'...$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/governance-$(PATTERN).xml"; \ if [ -n "$(DEBUG)" ]; then \ cd tests/governance && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v -test.run ".*$(PATTERN).*" || TEST_FAILED=1; \ else \ cd tests/governance && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../$$REPORT_FILE \ -- -v -run ".*$(PATTERN).*" || TEST_FAILED=1; \ fi; \ cd ../..; \ $(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \ junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \ $(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) "$(CYAN)Running all governance tests...$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/governance-all.xml"; \ if [ -n "$(DEBUG)" ]; then \ cd tests/governance && GOWORK=off dlv test --headless --listen=:2345 --api-version=2 -- -test.v || TEST_FAILED=1; \ else \ cd tests/governance && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../$$REPORT_FILE \ -- -v || TEST_FAILED=1; \ fi; \ cd ../..; \ $(MAKE) cleanup-junit-xml REPORT_FILE=$$REPORT_FILE; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \ junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \ $(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ fi; \ if [ -f "$$REPORT_FILE" ]; then \ ALL_FAILED=$$(grep -B 1 '/dev/null | \ grep ' /dev/null; then \ $(ECHO) "$(GREEN) ✓ $$mcp_name$(NC)"; \ else \ $(ECHO) "$(RED) ✗ $$mcp_name failed$(NC)"; \ FAILED=1; \ cd - > /dev/null 2>&1 || true; \ fi; \ elif [ -f "$$mcp_dir/package.json" ]; then \ $(ECHO) "$(CYAN)Building $$mcp_name (TypeScript)...$(NC)"; \ if cd "$$mcp_dir" && npm install --silent && npm run build && cd - > /dev/null; then \ $(ECHO) "$(GREEN) ✓ $$mcp_name$(NC)"; \ else \ $(ECHO) "$(RED) ✗ $$mcp_name failed$(NC)"; \ FAILED=1; \ cd - > /dev/null 2>&1 || true; \ fi; \ fi; \ fi; \ done; \ if [ $$FAILED -eq 1 ]; then \ $(ECHO) "$(RED)Some MCP test servers failed to build$(NC)"; \ exit 1; \ fi @$(ECHO) "" @$(ECHO) "$(GREEN)✓ All MCP test servers built$(NC)" test-mcp: install-gotestsum setup-mcp-tests ## Run MCP tests (Usage: make test-mcp [TYPE=connection] [TESTCASE=TestName] [PATTERN=substring]) @$(ECHO) "$(GREEN)Running MCP tests...$(NC)" @mkdir -p $(TEST_REPORTS_DIR) @if [ -n "$(PATTERN)" ] && [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(RED)Error: PATTERN and TESTCASE are mutually exclusive$(NC)"; \ $(ECHO) "$(YELLOW)Use PATTERN for substring matching or TESTCASE for exact match$(NC)"; \ exit 1; \ fi @if [ ! -d "core/internal/mcptests" ]; then \ $(ECHO) "$(RED)Error: MCP tests directory not found$(NC)"; \ exit 1; \ fi @TEST_FAILED=0; \ REPORT_FILE=""; \ if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ if [ -n "$(TYPE)" ]; then \ TYPE_CLEAN=$$(echo $(TYPE) | sed 's/_test\.go$$//'); \ TEST_FILE="core/internal/mcptests/$${TYPE_CLEAN}_test.go"; \ if [ ! -f "$$TEST_FILE" ]; then \ $(ECHO) "$(RED)Error: Test file '$$TEST_FILE' not found$(NC)"; \ $(ECHO) "$(YELLOW)Available test types:$(NC)"; \ ls -1 core/internal/mcptests/*_test.go 2>/dev/null | sed 's|core/internal/mcptests/||' | sed 's|_test\.go$$||' | sed 's/^/ - /'; \ exit 1; \ fi; \ TEST_PATTERN=$$(grep -h "^func Test" $$TEST_FILE 2>/dev/null | sed 's/func \(Test[^(]*\).*/\1/' | paste -sd '|' - || $(ECHO) "^Test"); \ if [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(CYAN)Running $(TYPE) test: $(TESTCASE)...$(NC)"; \ SAFE_TESTCASE=$$($(ECHO) "$(TESTCASE)" | sed 's|/|_|g'); \ REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-$(TYPE)-$$SAFE_TESTCASE.xml"; \ cd core/internal/mcptests && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../../$$REPORT_FILE \ -- -v -race -run "^$(TESTCASE)$$" . || TEST_FAILED=1; \ elif [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running $(TYPE) tests matching '$(PATTERN)'...$(NC)"; \ SAFE_PATTERN=$$($(ECHO) "$(PATTERN)" | sed 's|/|_|g'); \ REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-$(TYPE)-$$SAFE_PATTERN.xml"; \ cd core/internal/mcptests && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../../$$REPORT_FILE \ -- -v -race -run ".*$(PATTERN).*" . || TEST_FAILED=1; \ else \ $(ECHO) "$(CYAN)Running all $(TYPE) tests (pattern: $$TEST_PATTERN)...$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-$(TYPE).xml"; \ cd core/internal/mcptests && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../../$$REPORT_FILE \ -- -v -race -run "$$TEST_PATTERN" . || TEST_FAILED=1; \ fi; \ cd ../../..; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \ junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \ $(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ if [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(CYAN)Running test case: $(TESTCASE) across all MCP tests...$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-all-$(TESTCASE).xml"; \ cd core/internal/mcptests && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../../$$REPORT_FILE \ -- -v -race -run "^$(TESTCASE)$$" || TEST_FAILED=1; \ elif [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running tests matching '$(PATTERN)' across all MCP tests...$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-all-$(PATTERN).xml"; \ cd core/internal/mcptests && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../../$$REPORT_FILE \ -- -v -race -run ".*$(PATTERN).*" || TEST_FAILED=1; \ else \ $(ECHO) "$(CYAN)Running all MCP tests...$(NC)"; \ REPORT_FILE="$(TEST_REPORTS_DIR)/mcp-all.xml"; \ cd core/internal/mcptests && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../../../$$REPORT_FILE \ -- -v -race || TEST_FAILED=1; \ fi; \ cd ../../..; \ if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ if which junit-viewer > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)Generating HTML report...$(NC)"; \ junit-viewer --results=$$REPORT_FILE --save=$${REPORT_FILE%.xml}.html 2>/dev/null || true; \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML report: $${REPORT_FILE%.xml}.html$(NC)"; \ $(ECHO) "$(CYAN)Open with: open $${REPORT_FILE%.xml}.html$(NC)"; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ else \ $(ECHO) ""; \ $(ECHO) "$(CYAN)JUnit XML report: $$REPORT_FILE$(NC)"; \ fi; \ fi; \ if [ $$TEST_FAILED -eq 1 ]; then \ exit 1; \ fi test-all: test-core test-framework test-plugins test-http-transport test test-cli ## Run all tests @$(ECHO) "" @$(ECHO) "$(GREEN)═══════════════════════════════════════════════════════════$(NC)" @$(ECHO) "$(GREEN) All Tests Complete - Summary $(NC)" @$(ECHO) "$(GREEN)═══════════════════════════════════════════════════════════$(NC)" @$(ECHO) "" @if [ -z "$$CI" ] && [ -z "$$GITHUB_ACTIONS" ] && [ -z "$$GITLAB_CI" ] && [ -z "$$CIRCLECI" ] && [ -z "$$JENKINS_HOME" ]; then \ $(ECHO) "$(YELLOW)Generating combined HTML report...$(NC)"; \ junit-viewer --results=$(TEST_REPORTS_DIR) --save=$(TEST_REPORTS_DIR)/index.html 2>/dev/null || true; \ $(ECHO) ""; \ $(ECHO) "$(CYAN)HTML reports available in $(TEST_REPORTS_DIR)/:$(NC)"; \ ls -1 $(TEST_REPORTS_DIR)/*.html 2>/dev/null | sed 's/^/ ✓ /' || $(ECHO) " No reports found"; \ $(ECHO) ""; \ $(ECHO) "$(YELLOW)📊 View all test results:$(NC)"; \ $(ECHO) "$(CYAN) open $(TEST_REPORTS_DIR)/index.html$(NC)"; \ $(ECHO) ""; \ $(ECHO) "$(YELLOW)Or view individual reports:$(NC)"; \ ls -1 $(TEST_REPORTS_DIR)/*.html 2>/dev/null | grep -v index.html | sed 's|$(TEST_REPORTS_DIR)/| open $(TEST_REPORTS_DIR)/|' || true; \ $(ECHO) ""; \ else \ $(ECHO) "$(CYAN)JUnit XML reports available in $(TEST_REPORTS_DIR)/:$(NC)"; \ ls -1 $(TEST_REPORTS_DIR)/*.xml 2>/dev/null | sed 's/^/ ✓ /' || $(ECHO) " No reports found"; \ $(ECHO) ""; \ fi test-chatbot: ## Run interactive chatbot integration test (Usage: RUN_CHATBOT_TEST=1 make test-chatbot) @$(ECHO) "$(GREEN)Running interactive chatbot integration test...$(NC)" @if [ -z "$(RUN_CHATBOT_TEST)" ]; then \ $(ECHO) "$(YELLOW)⚠️ This is an interactive test. Set RUN_CHATBOT_TEST=1 to run it.$(NC)"; \ $(ECHO) "$(CYAN)Usage: RUN_CHATBOT_TEST=1 make test-chatbot$(NC)"; \ $(ECHO) ""; \ $(ECHO) "$(YELLOW)Required environment variables:$(NC)"; \ $(ECHO) " - OPENAI_API_KEY (required)"; \ $(ECHO) " - ANTHROPIC_API_KEY (optional)"; \ $(ECHO) " - Additional provider keys as needed"; \ exit 0; \ fi @if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi @cd core && RUN_CHATBOT_TEST=1 go test -v -run TestChatbot test-integrations-py: ## Run Python integration tests (Usage: make test-integrations-py [INTEGRATION=openai] [TESTCASE=test_name] [PATTERN=substring] [VERBOSE=1]) @$(ECHO) "$(GREEN)Running Python integration tests...$(NC)" @if [ ! -d "tests/integrations/python" ]; then \ $(ECHO) "$(RED)Error: tests/integrations/python directory not found$(NC)"; \ exit 1; \ fi; \ if [ -n "$(PATTERN)" ] && [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(RED)Error: PATTERN and TESTCASE are mutually exclusive$(NC)"; \ $(ECHO) "$(YELLOW)Use PATTERN for substring matching or TESTCASE for exact match$(NC)"; \ exit 1; \ fi; \ if [ -n "$(TESTCASE)" ] && [ -z "$(INTEGRATION)" ]; then \ $(ECHO) "$(RED)Error: TESTCASE requires INTEGRATION to be specified$(NC)"; \ $(ECHO) "$(YELLOW)Usage: make test-integrations-py INTEGRATION=anthropic TESTCASE=test_05_end2end_tool_calling$(NC)"; \ exit 1; \ fi; \ if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ BIFROST_STARTED=0; \ BIFROST_PID=""; \ TAIL_PID=""; \ TEST_PORT=$${PORT:-8080}; \ TEST_HOST=$${HOST:-localhost}; \ $(ECHO) "$(CYAN)Checking if Bifrost is running on $$TEST_HOST:$$TEST_PORT...$(NC)"; \ if curl -s -o /dev/null -w "%{http_code}" http://$$TEST_HOST:$$TEST_PORT/health 2>/dev/null | grep -q "200\|404"; then \ $(ECHO) "$(GREEN)✓ Bifrost is already running$(NC)"; \ else \ $(ECHO) "$(YELLOW)Bifrost not running, starting it...$(NC)"; \ ./tmp/bifrost-http -host "$$TEST_HOST" -port "$$TEST_PORT" -log-style "$(LOG_STYLE)" -log-level "$(LOG_LEVEL)" -app-dir tests/integrations/python > /tmp/bifrost-test.log 2>&1 & \ BIFROST_PID=$$!; \ BIFROST_STARTED=1; \ $(ECHO) "$(YELLOW)Waiting for Bifrost to be ready...$(NC)"; \ $(ECHO) "$(CYAN)Bifrost logs: /tmp/bifrost-test.log$(NC)"; \ (tail -f /tmp/bifrost-test.log 2>/dev/null | grep -E "error|panic|Error|ERRO|fatal|Fatal|FATAL" --line-buffered &) & \ TAIL_PID=$$!; \ for i in 1 2 3 4 5 6 7 8 9 10; do \ if curl -s -o /dev/null http://$$TEST_HOST:$$TEST_PORT/health 2>/dev/null; then \ $(ECHO) "$(GREEN)✓ Bifrost is ready (PID: $$BIFROST_PID)$(NC)"; \ break; \ fi; \ if [ $$i -eq 10 ]; then \ $(ECHO) "$(RED)Failed to start Bifrost$(NC)"; \ $(ECHO) "$(YELLOW)Bifrost logs:$(NC)"; \ cat /tmp/bifrost-test.log 2>/dev/null || $(ECHO) "No log file found"; \ [ -n "$$BIFROST_PID" ] && kill $$BIFROST_PID 2>/dev/null; \ [ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null; \ exit 1; \ fi; \ sleep 1; \ done; \ fi; \ TEST_FAILED=0; \ if ! which uv > /dev/null 2>&1; then \ $(ECHO) "$(YELLOW)uv not found, checking for pytest...$(NC)"; \ if ! which pytest > /dev/null 2>&1; then \ $(ECHO) "$(RED)Error: Neither uv nor pytest found$(NC)"; \ $(ECHO) "$(YELLOW)Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh$(NC)"; \ $(ECHO) "$(YELLOW)Or install pytest: pip install pytest$(NC)"; \ [ $$BIFROST_STARTED -eq 1 ] && [ -n "$$BIFROST_PID" ] && kill $$BIFROST_PID 2>/dev/null; \ [ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null; \ exit 1; \ fi; \ $(ECHO) "$(CYAN)Using pytest directly$(NC)"; \ if [ -n "$(INTEGRATION)" ]; then \ if [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(CYAN)Running $(INTEGRATION) integration test: $(TESTCASE)...$(NC)"; \ cd tests/integrations/python && pytest tests/test_$(INTEGRATION).py::$(TESTCASE) $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \ elif [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests matching '$(PATTERN)'...$(NC)"; \ cd tests/integrations/python && pytest tests/test_$(INTEGRATION).py -k "$(PATTERN)" $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \ else \ $(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests...$(NC)"; \ cd tests/integrations/python && pytest tests/test_$(INTEGRATION).py $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \ fi; \ else \ if [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running all integration tests matching '$(PATTERN)'...$(NC)"; \ cd tests/integrations/python && pytest -k "$(PATTERN)" $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \ else \ $(ECHO) "$(CYAN)Running all integration tests...$(NC)"; \ cd tests/integrations/python && pytest $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \ fi; \ fi; \ else \ $(ECHO) "$(CYAN)Using uv (fast mode)$(NC)"; \ cd tests/integrations/python && \ if [ -n "$(INTEGRATION)" ]; then \ if [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(CYAN)Running $(INTEGRATION) integration test: $(TESTCASE)...$(NC)"; \ uv run pytest tests/test_$(INTEGRATION).py::$(TESTCASE) $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \ elif [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests matching '$(PATTERN)'...$(NC)"; \ uv run pytest tests/test_$(INTEGRATION).py -k "$(PATTERN)" $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \ else \ $(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests...$(NC)"; \ uv run pytest tests/test_$(INTEGRATION).py $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \ fi; \ else \ if [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running all integration tests matching '$(PATTERN)'...$(NC)"; \ uv run pytest -k "$(PATTERN)" $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \ else \ $(ECHO) "$(CYAN)Running all integration tests...$(NC)"; \ uv run pytest $(if $(VERBOSE),-v,-q) || TEST_FAILED=1; \ fi; \ fi; \ fi; \ if [ $$BIFROST_STARTED -eq 1 ] && [ -n "$$BIFROST_PID" ]; then \ $(ECHO) "$(YELLOW)Stopping Bifrost (PID: $$BIFROST_PID)...$(NC)"; \ kill $$BIFROST_PID 2>/dev/null || true; \ [ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null || true; \ wait $$BIFROST_PID 2>/dev/null || true; \ $(ECHO) "$(GREEN)✓ Bifrost stopped$(NC)"; \ if [ $$TEST_FAILED -eq 1 ]; then \ $(ECHO) ""; \ $(ECHO) "$(YELLOW)Last 50 lines of Bifrost logs:$(NC)"; \ tail -50 /tmp/bifrost-test.log 2>/dev/null || $(ECHO) "No log file found"; \ fi; \ fi; \ $(ECHO) ""; \ if [ $$TEST_FAILED -eq 1 ]; then \ $(ECHO) "$(RED)✗ Integration tests failed$(NC)"; \ $(ECHO) "$(CYAN)Full Bifrost logs: /tmp/bifrost-test.log$(NC)"; \ exit 1; \ else \ $(ECHO) "$(GREEN)✓ Integration tests complete$(NC)"; \ fi test-integrations-ts: ## Run TypeScript integration tests (Usage: make test-integrations-ts [INTEGRATION=openai] [TESTCASE=test_name] [PATTERN=substring] [VERBOSE=1]) @$(ECHO) "$(GREEN)Running TypeScript integration tests...$(NC)" @if [ ! -d "tests/integrations/typescript" ]; then \ $(ECHO) "$(RED)Error: tests/integrations/typescript directory not found$(NC)"; \ exit 1; \ fi; \ if [ -n "$(PATTERN)" ] && [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(RED)Error: PATTERN and TESTCASE are mutually exclusive$(NC)"; \ $(ECHO) "$(YELLOW)Use PATTERN for substring matching or TESTCASE for exact match$(NC)"; \ exit 1; \ fi; \ if [ -n "$(TESTCASE)" ] && [ -z "$(INTEGRATION)" ]; then \ $(ECHO) "$(RED)Error: TESTCASE requires INTEGRATION to be specified$(NC)"; \ $(ECHO) "$(YELLOW)Usage: make test-integrations-ts INTEGRATION=openai TESTCASE=test_simple_chat$(NC)"; \ exit 1; \ fi; \ if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ BIFROST_STARTED=0; \ BIFROST_PID=""; \ TAIL_PID=""; \ TEST_PORT=$${PORT:-8080}; \ TEST_HOST=$${HOST:-localhost}; \ $(ECHO) "$(CYAN)Checking if Bifrost is running on $$TEST_HOST:$$TEST_PORT...$(NC)"; \ if curl -s -o /dev/null -w "%{http_code}" http://$$TEST_HOST:$$TEST_PORT/health 2>/dev/null | grep -q "200\|404"; then \ $(ECHO) "$(GREEN)✓ Bifrost is already running$(NC)"; \ else \ $(ECHO) "$(YELLOW)Bifrost not running, starting it...$(NC)"; \ ./tmp/bifrost-http -host "$$TEST_HOST" -port "$$TEST_PORT" -log-style "$(LOG_STYLE)" -log-level "$(LOG_LEVEL)" -app-dir tests/integrations/typescript > /tmp/bifrost-test.log 2>&1 & \ BIFROST_PID=$$!; \ BIFROST_STARTED=1; \ $(ECHO) "$(YELLOW)Waiting for Bifrost to be ready...$(NC)"; \ $(ECHO) "$(CYAN)Bifrost logs: /tmp/bifrost-test.log$(NC)"; \ (tail -f /tmp/bifrost-test.log 2>/dev/null | grep -E "error|panic|Error|ERRO|fatal|Fatal|FATAL" --line-buffered &) & \ TAIL_PID=$$!; \ for i in 1 2 3 4 5 6 7 8 9 10; do \ if curl -s -o /dev/null http://$$TEST_HOST:$$TEST_PORT/health 2>/dev/null; then \ $(ECHO) "$(GREEN)✓ Bifrost is ready (PID: $$BIFROST_PID)$(NC)"; \ break; \ fi; \ if [ $$i -eq 10 ]; then \ $(ECHO) "$(RED)Failed to start Bifrost$(NC)"; \ $(ECHO) "$(YELLOW)Bifrost logs:$(NC)"; \ cat /tmp/bifrost-test.log 2>/dev/null || $(ECHO) "No log file found"; \ [ -n "$$BIFROST_PID" ] && kill $$BIFROST_PID 2>/dev/null; \ [ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null; \ exit 1; \ fi; \ sleep 1; \ done; \ fi; \ TEST_FAILED=0; \ $(USE_NODE); \ if ! which npm > /dev/null 2>&1; then \ $(ECHO) "$(RED)Error: npm not found$(NC)"; \ $(ECHO) "$(YELLOW)Install Node.js: https://nodejs.org/$(NC)"; \ [ $$BIFROST_STARTED -eq 1 ] && [ -n "$$BIFROST_PID" ] && kill $$BIFROST_PID 2>/dev/null; \ [ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null; \ exit 1; \ fi; \ $(ECHO) "$(CYAN)Using npm$(NC)"; \ cd tests/integrations/typescript && \ if [ ! -d "node_modules" ]; then \ $(ECHO) "$(YELLOW)Installing dependencies...$(NC)"; \ npm install; \ fi; \ if [ -n "$(INTEGRATION)" ]; then \ if [ -n "$(TESTCASE)" ]; then \ $(ECHO) "$(CYAN)Running $(INTEGRATION) integration test: $(TESTCASE)...$(NC)"; \ npm test -- tests/test-$(INTEGRATION).test.ts -t "$(TESTCASE)" $(if $(VERBOSE),--reporter=verbose,) || TEST_FAILED=1; \ elif [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests matching '$(PATTERN)'...$(NC)"; \ npm test -- tests/test-$(INTEGRATION).test.ts -t "$(PATTERN)" $(if $(VERBOSE),--reporter=verbose,) || TEST_FAILED=1; \ else \ $(ECHO) "$(CYAN)Running $(INTEGRATION) integration tests...$(NC)"; \ npm test -- tests/test-$(INTEGRATION).test.ts $(if $(VERBOSE),--reporter=verbose,) || TEST_FAILED=1; \ fi; \ else \ if [ -n "$(PATTERN)" ]; then \ $(ECHO) "$(CYAN)Running all integration tests matching '$(PATTERN)'...$(NC)"; \ npm test -- -t "$(PATTERN)" $(if $(VERBOSE),--reporter=verbose,) || TEST_FAILED=1; \ else \ $(ECHO) "$(CYAN)Running all integration tests...$(NC)"; \ npm test $(if $(VERBOSE),-- --reporter=verbose,) || TEST_FAILED=1; \ fi; \ fi; \ if [ $$BIFROST_STARTED -eq 1 ] && [ -n "$$BIFROST_PID" ]; then \ $(ECHO) "$(YELLOW)Stopping Bifrost (PID: $$BIFROST_PID)...$(NC)"; \ kill $$BIFROST_PID 2>/dev/null || true; \ [ -n "$$TAIL_PID" ] && kill $$TAIL_PID 2>/dev/null || true; \ wait $$BIFROST_PID 2>/dev/null || true; \ $(ECHO) "$(GREEN)✓ Bifrost stopped$(NC)"; \ if [ $$TEST_FAILED -eq 1 ]; then \ $(ECHO) ""; \ $(ECHO) "$(YELLOW)Last 50 lines of Bifrost logs:$(NC)"; \ tail -50 /tmp/bifrost-test.log 2>/dev/null || $(ECHO) "No log file found"; \ fi; \ fi; \ $(ECHO) ""; \ if [ $$TEST_FAILED -eq 1 ]; then \ $(ECHO) "$(RED)✗ TypeScript integration tests failed$(NC)"; \ $(ECHO) "$(CYAN)Full Bifrost logs: /tmp/bifrost-test.log$(NC)"; \ exit 1; \ else \ $(ECHO) "$(GREEN)✓ TypeScript integration tests complete$(NC)"; \ fi install-playwright: ## Install Playwright test dependencies @$(ECHO) "$(GREEN)Installing Playwright dependencies...$(NC)" @which node > /dev/null || ($(ECHO) "$(RED)Error: Node.js is not installed. Please install Node.js first.$(NC)" && exit 1) @which npm > /dev/null || ($(ECHO) "$(RED)Error: npm is not installed. Please install npm first.$(NC)" && exit 1) @$(USE_NODE); cd tests/e2e && npm ci @cd tests/e2e && if npx playwright install --list 2>/dev/null | grep -q "chromium"; then \ $(ECHO) "$(CYAN)Chromium is already installed, skipping download$(NC)"; \ else \ $(ECHO) "$(CYAN)Installing Chromium...$(NC)"; \ npx playwright install --with-deps chromium; \ fi @$(ECHO) "$(GREEN)Playwright is ready$(NC)" build-test-plugin: ## Build test plugin for E2E tests (copies to tmp/bifrost-test-plugin.so) @$(ECHO) "$(GREEN)Building test plugin for E2E tests...$(NC)" @cd examples/plugins/hello-world && make dev @mkdir -p tmp @cp examples/plugins/hello-world/build/hello-world.so tmp/bifrost-test-plugin.so @$(ECHO) "$(GREEN)✓ Test plugin ready at tmp/bifrost-test-plugin.so$(NC)" run-e2e: install-playwright ## Run E2E tests (Usage: make run-e2e [FLOW=providers|virtual-keys|config]) @$(ECHO) "$(GREEN)Running Playwright E2E tests...$(NC)" @if [ -n "$(FLOW)" ]; then \ $(ECHO) "$(CYAN)Running $(FLOW) tests...$(NC)"; \ if [ "$(FLOW)" = "config" ]; then \ cd tests/e2e && npx playwright test --project=chromium-config; \ else \ cd tests/e2e && npx playwright test features/$(FLOW); \ fi; \ else \ $(ECHO) "$(CYAN)Running all E2E tests...$(NC)"; \ cd tests/e2e && npx playwright test; \ fi @$(ECHO) "" @$(ECHO) "$(GREEN)E2E tests complete$(NC)" @$(ECHO) "$(CYAN)View HTML report: cd tests/e2e && npx playwright show-report$(NC)" run-e2e-ui: install-playwright ## Run E2E tests in interactive UI mode @if [ -f .env ]; then \ $(ECHO) "$(YELLOW)Loading environment variables from .env...$(NC)"; \ set -a; . ./.env; set +a; \ fi; \ $(ECHO) "$(GREEN)Opening Playwright UI...$(NC)"; \ cd tests/e2e && npx playwright test --ui run-e2e-headed: install-playwright ## Run E2E tests in headed browser mode @$(ECHO) "$(GREEN)Running E2E tests in headed mode...$(NC)" @if [ -n "$(FLOW)" ]; then \ $(ECHO) "$(CYAN)Running $(FLOW) tests (headed)...$(NC)"; \ if [ "$(FLOW)" = "config" ]; then \ cd tests/e2e && npx playwright test --project=chromium-config --headed; \ else \ cd tests/e2e && npx playwright test features/$(FLOW) --headed; \ fi; \ else \ $(ECHO) "$(CYAN)Running all E2E tests (headed)...$(NC)"; \ cd tests/e2e && npx playwright test --headed; \ fi # Quick start with example config quick-start: ## Quick start with example config and maxim plugin @$(ECHO) "$(GREEN)Quick starting Bifrost with example configuration...$(NC)" @$(MAKE) dev # Linting and formatting lint: ## Run linter for Go code @$(ECHO) "$(GREEN)Running golangci-lint...$(NC)" @golangci-lint run ./... fmt: ## Format Go code @$(ECHO) "$(GREEN)Formatting Go code...$(NC)" @gofmt -s -w . @goimports -w . format: ## Format code (Usage: make format ui) ifeq (ui,$(filter ui,$(MAKECMDGOALS))) @$(ECHO) "$(GREEN)Formatting UI code...$(NC)" @cd ui && $(USE_NODE); npm run format else @$(ECHO) "$(YELLOW)Usage: make format ui$(NC)" endif ui: @: # Workspace helpers setup-workspace: ## Set up Go workspace with all local modules for development @$(ECHO) "$(GREEN)Setting up Go workspace for local development...$(NC)" @$(ECHO) "$(YELLOW)Cleaning existing workspace...$(NC)" @rm -f go.work go.work.sum || true @$(ECHO) "$(YELLOW)Initializing new workspace...$(NC)" @go work init ./cli ./core ./framework ./transports @$(ECHO) "$(YELLOW)Adding plugin modules...$(NC)" @for plugin_dir in ./plugins/*/; do \ if [ -d "$$plugin_dir" ] && [ -f "$$plugin_dir/go.mod" ]; then \ $(ECHO) " Adding plugin: $$(basename $$plugin_dir)"; \ go work use "$$plugin_dir"; \ fi; \ done @$(ECHO) "$(YELLOW)Syncing workspace...$(NC)" @go work sync @$(ECHO) "$(GREEN)✓ Go workspace ready with all local modules$(NC)" @$(ECHO) "" @$(ECHO) "$(CYAN)Local modules in workspace:$(NC)" @go list -m all | grep "github.com/maximhq/bifrost" | grep -v " v" | sed 's/^/ ✓ /' @$(ECHO) "" @$(ECHO) "$(CYAN)Remote modules (no local version):$(NC)" @go list -m all | grep "github.com/maximhq/bifrost" | grep " v" | sed 's/^/ → /' @$(ECHO) "" @$(ECHO) "$(YELLOW)Note: go.work files are not committed to version control$(NC)" work-init: ## Create local go.work to use local modules for development (legacy) @$(ECHO) "$(YELLOW)⚠️ work-init is deprecated, use 'make setup-workspace' instead$(NC)" @$(MAKE) setup-workspace work-clean: ## Remove local go.work @rm -f go.work go.work.sum || true @$(ECHO) "$(GREEN)Removed local go.work files$(NC)" # Module parameter for mod-tidy (all/core/plugins/framework/transport) MODULE ?= all mod-tidy: ## Run go mod tidy on modules (Usage: make mod-tidy [MODULE=all|cli|core|plugins|framework|transport]) @$(ECHO) "$(GREEN)Running go mod tidy...$(NC)" @if [ "$(MODULE)" = "all" ] || [ "$(MODULE)" = "cli" ]; then \ $(ECHO) "$(CYAN)Tidying cli...$(NC)"; \ cd cli && $(if $(LOCAL),,GOWORK=off) go mod tidy && $(ECHO) "$(GREEN) ✓ cli$(NC)"; \ fi @if [ "$(MODULE)" = "all" ] || [ "$(MODULE)" = "core" ]; then \ $(ECHO) "$(CYAN)Tidying core...$(NC)"; \ cd core && go mod tidy && $(ECHO) "$(GREEN) ✓ core$(NC)"; \ fi @if [ "$(MODULE)" = "all" ] || [ "$(MODULE)" = "framework" ]; then \ $(ECHO) "$(CYAN)Tidying framework...$(NC)"; \ cd framework && go mod tidy && $(ECHO) "$(GREEN) ✓ framework$(NC)"; \ fi @if [ "$(MODULE)" = "all" ] || [ "$(MODULE)" = "transport" ]; then \ $(ECHO) "$(CYAN)Tidying transports...$(NC)"; \ cd transports && go mod tidy && $(ECHO) "$(GREEN) ✓ transports$(NC)"; \ fi @if [ "$(MODULE)" = "all" ] || [ "$(MODULE)" = "plugins" ]; then \ $(ECHO) "$(CYAN)Tidying plugins...$(NC)"; \ for plugin_dir in ./plugins/*/; do \ if [ -d "$$plugin_dir" ] && [ -f "$$plugin_dir/go.mod" ]; then \ plugin_name=$$(basename $$plugin_dir); \ cd $$plugin_dir && go mod tidy && cd ../.. && $(ECHO) "$(GREEN) ✓ plugins/$$plugin_name$(NC)"; \ fi; \ done; \ fi @$(ECHO) "" @$(ECHO) "$(GREEN)✓ go mod tidy complete$(NC)" test-cli: install-gotestsum ## Run CLI tests @$(ECHO) "$(GREEN)Running CLI tests...$(NC)" @mkdir -p $(TEST_REPORTS_DIR) @cd cli && GOWORK=off gotestsum \ --format=$(GOTESTSUM_FORMAT) \ --junitfile=../$(TEST_REPORTS_DIR)/cli.xml \ -- ./...