From 1c5a35f2b510d9289d8820e1330542e49b94f3b1 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Thu, 26 Feb 2026 17:56:42 -0500 Subject: [PATCH] make client Makefiles self-bootstrapping: venv, npm install, go detect Python: auto-creates .venv with pytest, requests, aiohttp. JavaScript: auto npm install when node_modules missing, uses npm test for ESM. Go: auto-detects go binary from PATH/~/.local/go/usr/local/go, copies tests into src/ for same-package constraint, adds go.mod for sync SDK. --- .gitignore | 1 + clients/go/Makefile | 113 +++++++++++++++++++------------ clients/go/sync/src/go.mod | 3 + clients/javascript/Makefile | 80 +++++++++------------- clients/python/Makefile | 130 +++++++++++++++++------------------- 5 files changed, 169 insertions(+), 158 deletions(-) create mode 100644 clients/go/sync/src/go.mod diff --git a/.gitignore b/.gitignore index 1f4d4e5..38ba079 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ /Un.class __pycache__/ +.venv/ # Build directories _build/ diff --git a/clients/go/Makefile b/clients/go/Makefile index 11b3472..ff873a3 100644 --- a/clients/go/Makefile +++ b/clients/go/Makefile @@ -5,17 +5,13 @@ # - async/ : Asynchronous Go SDK (goroutines/channels) # # Usage: -# make # Build all -# make test # Run all 4 test modes +# make test # Run all 4 test modes (auto-detects go binary) # make test-cli # CLI mode only # make test-library # Library mode only -# make test-integration # Integration mode only -# make test-functional # Functional mode only # make build # Build binaries # make clean # Remove build artifacts # -# Dependencies: -# Go 1.18+ (for generics support) +# The Makefile auto-detects go from PATH, ~/.local/go, /usr/local/go. .PHONY: all build test test-cli test-library test-integration test-functional .PHONY: test-sync test-async clean help examples fmt vet @@ -25,8 +21,11 @@ ROOT_DIR := $(shell cd ../.. && pwd) SYNC_DIR := sync ASYNC_DIR := async -# Go settings -GO := go +# Auto-detect Go binary: PATH first, then common install locations +GO := $(or $(shell which go 2>/dev/null), \ + $(shell test -x $(HOME)/.local/go/bin/go && echo $(HOME)/.local/go/bin/go), \ + $(shell test -x /usr/local/go/bin/go && echo /usr/local/go/bin/go), \ + $(shell test -x $(HOME)/go/bin/go && echo $(HOME)/go/bin/go)) GOFLAGS := -v # Colors @@ -40,6 +39,14 @@ NC := \033[0m help: @echo "UN Go Client - Build and Test" @echo "" + @if [ -n "$(GO)" ]; then \ + echo " Go binary: $(GO)"; \ + $(GO) version; \ + else \ + echo " $(RED)✗ Go binary not found$(NC)"; \ + echo " Install Go or set PATH to include go binary"; \ + fi + @echo "" @echo "Build:" @echo " make build Build all binaries" @echo " make build-sync Build sync SDK" @@ -62,18 +69,25 @@ help: @echo "" @echo "Utility:" @echo " make clean Remove build artifacts" - @echo " make deps Show required dependencies" - @echo " make examples Run examples" @echo "" +# Guard: fail early if no Go binary found +check-go: + @if [ -z "$(GO)" ]; then \ + echo "$(RED)✗ Go binary not found$(NC)"; \ + echo " Searched: PATH, ~/.local/go/bin, /usr/local/go/bin, ~/go/bin"; \ + exit 1; \ + fi + +# Ensure go.mod exists for the sync SDK +$(SYNC_DIR)/go.mod: check-go + @if [ ! -f "$(SYNC_DIR)/go.mod" ] && [ -d "$(SYNC_DIR)/src" ]; then \ + echo "Initializing go module for sync SDK..."; \ + cd $(SYNC_DIR) && $(GO) mod init unsandbox.com/un 2>/dev/null || true; \ + fi + all: build -deps: - @echo "Required:" - @echo " Go 1.18+ (https://golang.org/dl/)" - @echo "" - @go version - # ============================================================================ # BUILD # ============================================================================ @@ -81,7 +95,7 @@ deps: build: build-sync build-async @echo "$(GREEN)✓ All Go SDKs built$(NC)" -build-sync: +build-sync: check-go $(SYNC_DIR)/go.mod @echo "Building sync SDK..." @if [ -f "$(SYNC_DIR)/src/un.go" ]; then \ cd $(SYNC_DIR)/src && $(GO) build $(GOFLAGS) -o ../un . 2>&1 | head -5 || true; \ @@ -90,7 +104,7 @@ build-sync: echo "$(YELLOW)⊘$(NC) Sync SDK source not found"; \ fi -build-async: +build-async: check-go @echo "Building async SDK..." @if [ -f "$(ASYNC_DIR)/src/un.go" ]; then \ cd $(ASYNC_DIR)/src && $(GO) build $(GOFLAGS) -o ../un . 2>&1 | head -5 || true; \ @@ -111,22 +125,19 @@ test: test-cli test-library test-integration test-functional # TEST: CLI Mode # ============================================================================ -test-cli: +test-cli: check-go @echo "" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "CLI MODE: Testing Go CLI interface" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "" - @# Test root-level un.go if it exists @if [ -f "$(ROOT_DIR)/un.go" ]; then \ cd $(ROOT_DIR) && $(GO) run un.go --help > /dev/null 2>&1 && echo " $(GREEN)✓$(NC) CLI: Root un.go --help works" || echo " $(YELLOW)⊘$(NC) CLI: Root un.go --help (check syntax)"; \ fi - @# Test sync SDK CLI @if [ -f "$(SYNC_DIR)/src/un.go" ]; then \ cd $(SYNC_DIR)/src && $(GO) build -o /tmp/un_test . 2>/dev/null && echo " $(GREEN)✓$(NC) CLI: Sync SDK compiles" || echo " $(RED)✗$(NC) CLI: Sync SDK compile failed"; \ rm -f /tmp/un_test; \ fi - @# Test async SDK CLI @if [ -f "$(ASYNC_DIR)/src/un.go" ]; then \ cd $(ASYNC_DIR)/src && $(GO) build -o /tmp/un_test . 2>/dev/null && echo " $(GREEN)✓$(NC) CLI: Async SDK compiles" || echo " $(YELLOW)⊘$(NC) CLI: Async SDK not yet buildable"; \ rm -f /tmp/un_test 2>/dev/null || true; \ @@ -136,26 +147,34 @@ test-cli: # TEST: Library Mode # ============================================================================ -test-library: +test-library: check-go @echo "" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "LIBRARY MODE: Testing Go package imports" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "" - @# Test sync SDK with go test - @if [ -d "$(SYNC_DIR)/src" ]; then \ - cd $(SYNC_DIR)/src && $(GO) test -v ./... 2>&1 | head -20 || echo " $(YELLOW)⊘$(NC) Library: No tests defined yet"; \ + @# Go requires test files in the same directory as the package. + @# Copy tests into src/ temporarily, run, clean up. + @if [ -d "$(SYNC_DIR)/tests" ] && [ -d "$(SYNC_DIR)/src" ]; then \ + cp $(SYNC_DIR)/tests/*_test.go $(SYNC_DIR)/src/ 2>/dev/null; \ + cd $(SYNC_DIR)/src && $(GO) test -short -v . 2>&1; \ + rm -f $(SYNC_DIR)/src/*_test.go; \ + elif [ -d "$(SYNC_DIR)/src" ]; then \ + cd $(SYNC_DIR)/src && $(GO) test -short -v . 2>&1 | head -20 || echo " $(YELLOW)⊘$(NC) Library: No tests defined yet"; \ fi - @# Test async SDK with go test - @if [ -d "$(ASYNC_DIR)/src" ]; then \ - cd $(ASYNC_DIR)/src && $(GO) test -v ./... 2>&1 | head -20 || echo " $(YELLOW)⊘$(NC) Library: Async tests not defined"; \ + @if [ -d "$(ASYNC_DIR)/tests" ] && [ -d "$(ASYNC_DIR)/src" ]; then \ + cp $(ASYNC_DIR)/tests/*_test.go $(ASYNC_DIR)/src/ 2>/dev/null; \ + cd $(ASYNC_DIR)/src && $(GO) test -short -v . 2>&1; \ + rm -f $(ASYNC_DIR)/src/*_test.go; \ + elif [ -d "$(ASYNC_DIR)/src" ]; then \ + cd $(ASYNC_DIR)/src && $(GO) test -short -v . 2>&1 | head -20 || echo " $(YELLOW)⊘$(NC) Library: Async tests not defined"; \ fi # ============================================================================ # TEST: Integration Mode # ============================================================================ -test-integration: +test-integration: check-go @echo "" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "INTEGRATION MODE: Testing API contract" @@ -167,7 +186,7 @@ test-integration: else \ echo " Testing API authentication..."; \ if [ -f "$(ROOT_DIR)/un.go" ]; then \ - cd $(ROOT_DIR) && $(GO) run un.go -s python -c 'print(42)' 2>&1 | grep -q "42" && echo " $(GREEN)✓$(NC) Integration: API auth works" || echo " $(YELLOW)⊘$(NC) Integration: Check API connectivity"; \ + cd $(ROOT_DIR) && $(GO) run un.go -s python -c 'print(42)' 2>&1 | grep -q "42" && echo " $(GREEN)✓$(NC) Integration: API auth works" || echo " $(RED)✗$(NC) Integration: Check API connectivity"; \ fi; \ fi @@ -175,7 +194,7 @@ test-integration: # TEST: Functional Mode # ============================================================================ -test-functional: +test-functional: check-go @echo "" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "FUNCTIONAL MODE: Real-world scenarios" @@ -185,8 +204,8 @@ test-functional: echo " $(YELLOW)⊘$(NC) Skipping (no API credentials)"; \ else \ echo " Running functional tests..."; \ - if [ -f "$(ROOT_DIR)/un.go" ]; then \ - cd $(ROOT_DIR) && $(GO) run un.go -s python -c 'def fib(n): return n if n<2 else fib(n-1)+fib(n-2); print(fib(10))' 2>&1 | grep -q "55" && echo " $(GREEN)✓$(NC) Functional: Fibonacci" || echo " $(YELLOW)⊘$(NC) Functional: Fibonacci (check output)"; \ + if [ -d "$(SYNC_DIR)/tests" ]; then \ + cd $(SYNC_DIR) && $(GO) test -v -run TestFunctional ./tests/ 2>&1 || true; \ fi; \ fi @@ -194,18 +213,26 @@ test-functional: # TEST: By SDK Type # ============================================================================ -test-sync: +test-sync: check-go @echo "Testing Sync SDK..." - @if [ -d "$(SYNC_DIR)/src" ]; then \ - cd $(SYNC_DIR)/src && $(GO) test -v ./...; \ + @if [ -d "$(SYNC_DIR)/tests" ] && [ -d "$(SYNC_DIR)/src" ]; then \ + cp $(SYNC_DIR)/tests/*_test.go $(SYNC_DIR)/src/ 2>/dev/null; \ + cd $(SYNC_DIR)/src && $(GO) test -v .; \ + rm -f $(SYNC_DIR)/src/*_test.go; \ + elif [ -d "$(SYNC_DIR)/src" ]; then \ + cd $(SYNC_DIR)/src && $(GO) test -v .; \ else \ echo " $(YELLOW)⊘$(NC) Sync SDK not found"; \ fi -test-async: +test-async: check-go @echo "Testing Async SDK..." - @if [ -d "$(ASYNC_DIR)/src" ]; then \ - cd $(ASYNC_DIR)/src && $(GO) test -v ./...; \ + @if [ -d "$(ASYNC_DIR)/tests" ] && [ -d "$(ASYNC_DIR)/src" ]; then \ + cp $(ASYNC_DIR)/tests/*_test.go $(ASYNC_DIR)/src/ 2>/dev/null; \ + cd $(ASYNC_DIR)/src && $(GO) test -v .; \ + rm -f $(ASYNC_DIR)/src/*_test.go; \ + elif [ -d "$(ASYNC_DIR)/src" ]; then \ + cd $(ASYNC_DIR)/src && $(GO) test -v .; \ else \ echo " $(YELLOW)⊘$(NC) Async SDK not found"; \ fi @@ -214,14 +241,14 @@ test-async: # Code Quality # ============================================================================ -fmt: +fmt: check-go @echo "Formatting Go code..." @if [ -d "$(SYNC_DIR)/src" ]; then gofmt -w $(SYNC_DIR)/src/; fi @if [ -d "$(ASYNC_DIR)/src" ]; then gofmt -w $(ASYNC_DIR)/src/; fi @if [ -f "$(ROOT_DIR)/un.go" ]; then gofmt -w $(ROOT_DIR)/un.go; fi @echo "$(GREEN)✓$(NC) Format complete" -vet: +vet: check-go @echo "Running go vet..." @if [ -d "$(SYNC_DIR)/src" ]; then cd $(SYNC_DIR)/src && $(GO) vet ./... 2>&1 || true; fi @if [ -d "$(ASYNC_DIR)/src" ]; then cd $(ASYNC_DIR)/src && $(GO) vet ./... 2>&1 || true; fi @@ -231,7 +258,7 @@ vet: # Examples # ============================================================================ -examples: +examples: check-go @echo "Running Go examples..." @if [ -d "$(SYNC_DIR)/examples" ]; then \ for f in $(SYNC_DIR)/examples/*.go; do \ diff --git a/clients/go/sync/src/go.mod b/clients/go/sync/src/go.mod new file mode 100644 index 0000000..9396c8c --- /dev/null +++ b/clients/go/sync/src/go.mod @@ -0,0 +1,3 @@ +module unsandbox.com/un + +go 1.23.6 diff --git a/clients/javascript/Makefile b/clients/javascript/Makefile index d5977f7..61beb44 100644 --- a/clients/javascript/Makefile +++ b/clients/javascript/Makefile @@ -5,21 +5,17 @@ # - async/ : Async/Await SDK (Node.js) # # Usage: -# make # Run all tests -# make test # Run all 4 test modes +# make test # Run all 4 test modes (auto-installs jest) # make test-cli # CLI mode only # make test-library # Library mode only -# make test-integration # Integration mode only -# make test-functional # Functional mode only # make test-sync # Test sync SDK only # make test-async # Test async SDK only -# make clean # Remove build artifacts +# make clean # Remove node_modules + build artifacts # -# Dependencies: -# npm install (or yarn install) +# The Makefile runs npm install automatically when node_modules is missing. .PHONY: all test test-cli test-library test-integration test-functional -.PHONY: test-sync test-async install dev-install lint format clean help examples +.PHONY: test-sync test-async lint format clean help examples # Paths ROOT_DIR := $(shell cd ../.. && pwd) @@ -38,7 +34,7 @@ help: @echo "UN JavaScript Client - Build and Test" @echo "" @echo "Test (all 4 modes):" - @echo " make test All 4 modes for both sync and async" + @echo " make test All 4 modes (auto-installs deps)" @echo " make test-cli CLI mode (command-line interface)" @echo " make test-library Library mode (require and use)" @echo " make test-integration Integration mode (API contract)" @@ -49,23 +45,30 @@ help: @echo " make test-async Test asynchronous SDK" @echo "" @echo "Development:" - @echo " make install Install dependencies" @echo " make lint Lint with ESLint" @echo " make format Format with Prettier" @echo " make examples Run example scripts" @echo "" @echo "Utility:" - @echo " make clean Remove build artifacts" - @echo " make deps Show required dependencies" + @echo " make clean Remove node_modules + build artifacts" @echo "" all: test -deps: - @echo "Required packages:" - @echo " npm install jest eslint prettier" - @echo "" - @node --version 2>/dev/null || echo "Node.js not installed" +# ============================================================================ +# Dependency Management +# ============================================================================ + +$(SYNC_DIR)/node_modules/.package-lock.json: $(SYNC_DIR)/package.json + @echo "Installing sync SDK dependencies..." + @cd $(SYNC_DIR) && npm install --no-audit --no-fund -q 2>&1 | tail -1 + +$(ASYNC_DIR)/node_modules/.package-lock.json: $(ASYNC_DIR)/package.json + @echo "Installing async SDK dependencies..." + @cd $(ASYNC_DIR) && npm install --no-audit --no-fund -q 2>&1 | tail -1 + +sync-deps: $(SYNC_DIR)/node_modules/.package-lock.json +async-deps: $(ASYNC_DIR)/node_modules/.package-lock.json # ============================================================================ # TEST: All 4 Modes @@ -85,15 +88,12 @@ test-cli: @echo "CLI MODE: Testing JavaScript CLI interface" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "" - @# Test root-level un.js if it exists @if [ -f "$(ROOT_DIR)/un.js" ]; then \ node --check "$(ROOT_DIR)/un.js" 2>/dev/null && echo " $(GREEN)✓$(NC) CLI: Syntax valid (un.js)" || echo " $(RED)✗$(NC) CLI: Syntax error in un.js"; \ fi - @# Test sync SDK syntax @if [ -f "$(SYNC_DIR)/src/un.js" ]; then \ node --check "$(SYNC_DIR)/src/un.js" 2>/dev/null && echo " $(GREEN)✓$(NC) CLI: Sync SDK syntax valid" || echo " $(RED)✗$(NC) CLI: Sync SDK syntax error"; \ fi - @# Test async SDK syntax (ES module with .mjs extension check) @if [ -f "$(ASYNC_DIR)/src/un_async.js" ]; then \ node --check "$(ASYNC_DIR)/src/un_async.js" 2>/dev/null && echo " $(GREEN)✓$(NC) CLI: Async SDK syntax valid" || echo " $(YELLOW)⊘$(NC) CLI: Async SDK ES module (use --input-type=module)"; \ fi @@ -102,30 +102,25 @@ test-cli: # TEST: Library Mode # ============================================================================ -test-library: +test-library: sync-deps @echo "" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "LIBRARY MODE: Testing JavaScript imports" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "" - @# Test sync SDK import @if [ -f "$(SYNC_DIR)/src/un.js" ]; then \ - node -e "const un = require('./$(SYNC_DIR)/src/un.js'); console.log(' ✓ Library: Sync SDK importable, exports:', Object.keys(un).length, 'functions')" 2>/dev/null || echo " $(YELLOW)⊘$(NC) Library: Sync import needs dependencies"; \ + node --input-type=module -e "const un = await import('./$(SYNC_DIR)/src/un.js'); const fns = Object.keys(un).filter(k => typeof un[k] === 'function'); console.log(' ✓ Library: Sync SDK importable, exports:', fns.length, 'functions')" 2>/dev/null || echo " $(RED)✗$(NC) Library: Sync import failed"; \ fi - @# Test async SDK import (ES module) @if [ -f "$(ASYNC_DIR)/src/un_async.js" ]; then \ - node --input-type=module -e "import un from './$(ASYNC_DIR)/src/un_async.js'; console.log(' ✓ Library: Async SDK importable, exports:', Object.keys(un).length, 'functions')" 2>/dev/null || echo " $(YELLOW)⊘$(NC) Library: Async import check (ES module)"; \ + node --input-type=module -e "const un = await import('./$(ASYNC_DIR)/src/un_async.js'); const fns = Object.keys(un).filter(k => typeof un[k] === 'function'); console.log(' ✓ Library: Async SDK importable, exports:', fns.length, 'functions')" 2>/dev/null || echo " $(YELLOW)⊘$(NC) Library: Async import check (ES module)"; \ fi - @# Run jest tests @echo "" @echo "Running unit tests..." @if [ -d "$(SYNC_DIR)/tests" ] && [ -f "$(SYNC_DIR)/package.json" ]; then \ - cd $(SYNC_DIR) && npm test 2>/dev/null && echo " $(GREEN)✓$(NC) Sync SDK tests passed" || echo " $(YELLOW)⊘$(NC) Sync tests need: npm install"; \ - elif [ -d "$(SYNC_DIR)/tests" ]; then \ - echo " $(YELLOW)⊘$(NC) Sync tests need package.json"; \ + cd $(SYNC_DIR) && npm test 2>&1 && echo " $(GREEN)✓$(NC) Sync SDK tests passed" || echo " $(RED)✗$(NC) Sync tests failed"; \ fi @if [ -d "$(ASYNC_DIR)/tests" ] && [ -f "$(ASYNC_DIR)/package.json" ]; then \ - cd $(ASYNC_DIR) && npm test 2>/dev/null && echo " $(GREEN)✓$(NC) Async SDK tests passed" || echo " $(YELLOW)⊘$(NC) Async tests need: npm install"; \ + cd $(ASYNC_DIR) && npm test 2>&1 && echo " $(GREEN)✓$(NC) Async SDK tests passed" || echo " $(RED)✗$(NC) Async tests failed"; \ fi # ============================================================================ @@ -144,7 +139,7 @@ test-integration: else \ echo " Testing API authentication..."; \ if [ -f "$(SYNC_DIR)/src/un.js" ]; then \ - node -e "const un = require('./$(SYNC_DIR)/src/un.js'); un.executeCode('python', 'print(42)').then(r => { if(r.stdout && r.stdout.includes('42')) console.log(' ✓ Integration: API auth works'); else console.log(' ✗ Integration: Unexpected response'); }).catch(e => console.log(' ✗ Integration:', e.message))" 2>/dev/null || echo " $(YELLOW)⊘$(NC) Integration: Check SDK"; \ + node --input-type=module -e "const un = await import('./$(SYNC_DIR)/src/un.js'); const r = await un.executeCode('python', 'print(42)'); if(r.stdout && r.stdout.includes('42')) console.log(' ✓ Integration: API auth works'); else console.log(' ✗ Integration: Unexpected response');" 2>/dev/null || echo " $(RED)✗$(NC) Integration: SDK error"; \ fi; \ fi @@ -163,7 +158,7 @@ test-functional: else \ echo " Running functional tests..."; \ if [ -f "$(SYNC_DIR)/src/un.js" ]; then \ - node -e "const un = require('./$(SYNC_DIR)/src/un.js'); un.executeCode('python', 'def fib(n): return n if n<2 else fib(n-1)+fib(n-2); print(fib(10))').then(r => { if(r.stdout && r.stdout.includes('55')) console.log(' ✓ Functional: Fibonacci'); else console.log(' ⊘ Functional: Check output'); }).catch(e => console.log(' ⊘ Functional:', e.message))" 2>/dev/null || echo " $(YELLOW)⊘$(NC) Functional: Check SDK"; \ + node --input-type=module -e "const un = await import('./$(SYNC_DIR)/src/un.js'); const r = await un.executeCode('python', 'def fib(n): return n if n<2 else fib(n-1)+fib(n-2); print(fib(10))'); if(r.stdout && r.stdout.includes('55')) console.log(' ✓ Functional: Fibonacci'); else console.log(' ✗ Functional: Check output');" 2>/dev/null || echo " $(RED)✗$(NC) Functional: SDK error"; \ fi; \ fi @@ -171,36 +166,26 @@ test-functional: # TEST: By SDK Type # ============================================================================ -test-sync: +test-sync: sync-deps @echo "Testing Sync SDK..." @if [ -f "$(SYNC_DIR)/package.json" ]; then \ cd $(SYNC_DIR) && npm test; \ - elif [ -d "$(SYNC_DIR)/tests" ]; then \ - echo " $(YELLOW)⊘$(NC) Sync SDK needs package.json with test script"; \ else \ - echo " $(YELLOW)⊘$(NC) Sync SDK tests not found"; \ + echo " $(YELLOW)⊘$(NC) Sync SDK needs package.json"; \ fi -test-async: +test-async: async-deps @echo "Testing Async SDK..." @if [ -f "$(ASYNC_DIR)/package.json" ]; then \ cd $(ASYNC_DIR) && npm test; \ - elif [ -d "$(ASYNC_DIR)/tests" ]; then \ - echo " $(YELLOW)⊘$(NC) Async SDK needs package.json with test script"; \ else \ - echo " $(YELLOW)⊘$(NC) Async SDK tests not found"; \ + echo " $(YELLOW)⊘$(NC) Async SDK needs package.json"; \ fi # ============================================================================ # Development # ============================================================================ -install: - @echo "Installing JavaScript SDK dependencies..." - @if [ -f "$(SYNC_DIR)/package.json" ]; then cd $(SYNC_DIR) && npm install; fi - @if [ -f "$(ASYNC_DIR)/package.json" ]; then cd $(ASYNC_DIR) && npm install; fi - @echo "$(GREEN)✓$(NC) Installation complete" - lint: @echo "Linting JavaScript SDKs..." @if [ -d "$(SYNC_DIR)/src" ]; then npx eslint $(SYNC_DIR)/src/ 2>/dev/null || echo " $(YELLOW)⊘$(NC) ESLint not configured"; fi @@ -230,5 +215,4 @@ clean: @echo "Cleaning JavaScript build artifacts..." @rm -rf $(SYNC_DIR)/node_modules $(ASYNC_DIR)/node_modules 2>/dev/null || true @rm -rf $(SYNC_DIR)/coverage $(ASYNC_DIR)/coverage 2>/dev/null || true - @rm -f $(SYNC_DIR)/package-lock.json $(ASYNC_DIR)/package-lock.json 2>/dev/null || true - @echo "$(GREEN)✓$(NC) Cleaned build artifacts" + @echo "$(GREEN)✓$(NC) Cleaned node_modules + build artifacts" diff --git a/clients/python/Makefile b/clients/python/Makefile index d8939c1..74fb3a3 100644 --- a/clients/python/Makefile +++ b/clients/python/Makefile @@ -5,26 +5,25 @@ # - async/ : Asynchronous Python SDK (aiohttp-based) # # Usage: -# make # Run all tests -# make test # Run all 4 test modes +# make test # Run all 4 test modes (auto-creates venv) # make test-cli # CLI mode only # make test-library # Library mode only -# make test-integration # Integration mode only -# make test-functional # Functional mode only # make test-sync # Test sync SDK only # make test-async # Test async SDK only -# make clean # Remove build artifacts +# make clean # Remove build artifacts + venv # -# Dependencies: -# pip install pytest pytest-cov pytest-asyncio aiohttp requests +# The Makefile manages a .venv automatically. No manual pip install needed. .PHONY: all test test-cli test-library test-integration test-functional -.PHONY: test-sync test-async install dev-install lint format clean help examples +.PHONY: test-sync test-async lint format clean help examples venv # Paths ROOT_DIR := $(shell cd ../.. && pwd) SYNC_DIR := sync ASYNC_DIR := async +VENV := .venv +PYTHON := $(VENV)/bin/python +PYTEST := $(VENV)/bin/pytest # Colors GREEN := \033[32m @@ -38,7 +37,7 @@ help: @echo "UN Python Client - Build and Test" @echo "" @echo "Test (all 4 modes):" - @echo " make test All 4 modes for both sync and async" + @echo " make test All 4 modes (auto-creates venv)" @echo " make test-cli CLI mode (command-line interface)" @echo " make test-library Library mode (import and use)" @echo " make test-integration Integration mode (API contract)" @@ -49,22 +48,39 @@ help: @echo " make test-async Test asynchronous SDK" @echo "" @echo "Development:" - @echo " make install Install both SDKs" - @echo " make dev-install Install with dev dependencies" + @echo " make venv Create/update virtual environment" @echo " make lint Lint both SDKs" @echo " make format Format both SDKs" @echo " make examples Run example scripts" @echo "" @echo "Utility:" - @echo " make clean Remove build artifacts" - @echo " make deps Show required dependencies" + @echo " make clean Remove build artifacts + venv" @echo "" all: test -deps: - @echo "Required packages:" - @echo " pip install pytest pytest-cov pytest-asyncio aiohttp requests black flake8 mypy" +# ============================================================================ +# Virtual Environment +# ============================================================================ + +$(VENV)/bin/activate: + @echo "Creating virtual environment..." + @python3 -m venv $(VENV) + @$(VENV)/bin/pip install --upgrade pip -q + +$(VENV)/.deps-installed: $(VENV)/bin/activate + @echo "Installing test dependencies into venv..." + @$(VENV)/bin/pip install -q requests pytest pytest-cov pytest-asyncio aiohttp + @if [ -f "$(SYNC_DIR)/setup.py" ]; then \ + cd $(SYNC_DIR) && ../$(VENV)/bin/pip install -q -e . 2>/dev/null || true; \ + fi + @if [ -f "$(ASYNC_DIR)/setup.py" ]; then \ + cd $(ASYNC_DIR) && ../$(VENV)/bin/pip install -q -e . 2>/dev/null || true; \ + fi + @touch $(VENV)/.deps-installed + +venv: $(VENV)/.deps-installed + @echo "$(GREEN)✓$(NC) Virtual environment ready at $(VENV)/" # ============================================================================ # TEST: All 4 Modes @@ -78,61 +94,55 @@ test: test-cli test-library test-integration test-functional # TEST: CLI Mode # ============================================================================ -test-cli: +test-cli: $(VENV)/.deps-installed @echo "" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "CLI MODE: Testing Python CLI interface" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "" - @# Test root-level un.py if it exists @if [ -f "$(ROOT_DIR)/un.py" ]; then \ - python3 -m py_compile "$(ROOT_DIR)/un.py" && echo " $(GREEN)✓$(NC) CLI: Syntax valid (un.py)"; \ - python3 "$(ROOT_DIR)/un.py" --help > /dev/null 2>&1 && echo " $(GREEN)✓$(NC) CLI: --help works" || echo " $(YELLOW)⊘$(NC) CLI: --help (may need API)"; \ + $(PYTHON) -m py_compile "$(ROOT_DIR)/un.py" && echo " $(GREEN)✓$(NC) CLI: Syntax valid (un.py)"; \ + $(PYTHON) "$(ROOT_DIR)/un.py" --help > /dev/null 2>&1 && echo " $(GREEN)✓$(NC) CLI: --help works" || echo " $(YELLOW)⊘$(NC) CLI: --help (may need API)"; \ else \ echo " $(YELLOW)⊘$(NC) Root un.py not found"; \ fi - @# Test sync SDK CLI @if [ -f "$(SYNC_DIR)/src/unsandbox/__main__.py" ]; then \ - python3 -m py_compile "$(SYNC_DIR)/src/unsandbox/__main__.py" && echo " $(GREEN)✓$(NC) CLI: Sync SDK syntax valid"; \ + $(PYTHON) -m py_compile "$(SYNC_DIR)/src/unsandbox/__main__.py" && echo " $(GREEN)✓$(NC) CLI: Sync SDK syntax valid"; \ fi - @# Test async SDK CLI @if [ -f "$(ASYNC_DIR)/src/un_async/__main__.py" ]; then \ - python3 -m py_compile "$(ASYNC_DIR)/src/un_async/__main__.py" && echo " $(GREEN)✓$(NC) CLI: Async SDK syntax valid"; \ + $(PYTHON) -m py_compile "$(ASYNC_DIR)/src/un_async/__main__.py" && echo " $(GREEN)✓$(NC) CLI: Async SDK syntax valid"; \ fi # ============================================================================ # TEST: Library Mode # ============================================================================ -test-library: +test-library: $(VENV)/.deps-installed @echo "" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "LIBRARY MODE: Testing Python imports" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "" - @# Test sync SDK import @if [ -d "$(SYNC_DIR)/src/unsandbox" ]; then \ - cd $(SYNC_DIR) && PYTHONPATH=src python3 -c "from unsandbox import UnsandboxClient; print(' ✓ Library: Sync UnsandboxClient importable')" 2>/dev/null || echo " $(YELLOW)⊘$(NC) Library: Sync import needs install"; \ + cd $(SYNC_DIR) && PYTHONPATH=src ../$(PYTHON) -c "from unsandbox import UnsandboxClient; print(' ✓ Library: Sync UnsandboxClient importable')" 2>/dev/null || echo " $(YELLOW)⊘$(NC) Library: Sync import failed"; \ fi - @# Test async SDK import @if [ -d "$(ASYNC_DIR)/src/un_async" ]; then \ - cd $(ASYNC_DIR) && PYTHONPATH=src python3 -c "from un_async import AsyncUnsandboxClient; print(' ✓ Library: Async AsyncUnsandboxClient importable')" 2>/dev/null || echo " $(YELLOW)⊘$(NC) Library: Async import needs install"; \ + cd $(ASYNC_DIR) && PYTHONPATH=src ../$(PYTHON) -c "from un_async import AsyncUnsandboxClient; print(' ✓ Library: Async AsyncUnsandboxClient importable')" 2>/dev/null || echo " $(YELLOW)⊘$(NC) Library: Async import failed"; \ fi - @# Run pytest for library tests @echo "" @echo "Running unit tests..." @if [ -d "$(SYNC_DIR)/tests" ]; then \ - cd $(SYNC_DIR) && pytest tests/ -q --tb=no 2>/dev/null && echo " $(GREEN)✓$(NC) Sync SDK tests passed" || echo " $(YELLOW)⊘$(NC) Sync tests need dependencies"; \ + cd $(SYNC_DIR) && PYTHONPATH=src ../$(PYTEST) tests/ -q --tb=short 2>&1 && echo " $(GREEN)✓$(NC) Sync SDK tests passed" || echo " $(RED)✗$(NC) Sync tests failed"; \ fi @if [ -d "$(ASYNC_DIR)/tests" ]; then \ - cd $(ASYNC_DIR) && pytest tests/ -q --tb=no 2>/dev/null && echo " $(GREEN)✓$(NC) Async SDK tests passed" || echo " $(YELLOW)⊘$(NC) Async tests need dependencies"; \ + cd $(ASYNC_DIR) && PYTHONPATH=src ../$(PYTEST) tests/ -q --tb=short 2>&1 && echo " $(GREEN)✓$(NC) Async SDK tests passed" || echo " $(RED)✗$(NC) Async tests failed"; \ fi # ============================================================================ # TEST: Integration Mode # ============================================================================ -test-integration: +test-integration: $(VENV)/.deps-installed @echo "" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "INTEGRATION MODE: Testing API contract" @@ -143,14 +153,14 @@ test-integration: echo " Set UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY"; \ else \ echo " Testing API authentication..."; \ - python3 -c "import sys; sys.path.insert(0, '$(SYNC_DIR)/src'); from unsandbox import UnsandboxClient; c = UnsandboxClient(); r = c.execute('python', 'print(42)'); print(' ✓ Integration: API auth works') if r else print(' ✗ Integration: API auth failed')" 2>/dev/null || echo " $(YELLOW)⊘$(NC) Integration: Need to install SDK first"; \ + $(PYTHON) -c "import sys; sys.path.insert(0, '$(SYNC_DIR)/src'); from unsandbox import UnsandboxClient; c = UnsandboxClient(); r = c.execute('python', 'print(42)'); print(' ✓ Integration: API auth works') if r else print(' ✗ Integration: API auth failed')" 2>/dev/null || echo " $(RED)✗$(NC) Integration: SDK error"; \ fi # ============================================================================ # TEST: Functional Mode # ============================================================================ -test-functional: +test-functional: $(VENV)/.deps-installed @echo "" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "FUNCTIONAL MODE: Real-world scenarios" @@ -161,7 +171,7 @@ test-functional: else \ echo " Running functional tests..."; \ if [ -f "$(SYNC_DIR)/verify_sdk.py" ]; then \ - cd $(SYNC_DIR) && python3 verify_sdk.py 2>/dev/null && echo " $(GREEN)✓$(NC) Functional: Sync SDK verified" || echo " $(YELLOW)⊘$(NC) Functional: Sync verification incomplete"; \ + cd $(SYNC_DIR) && ../$(PYTHON) verify_sdk.py 2>/dev/null && echo " $(GREEN)✓$(NC) Functional: Sync SDK verified" || echo " $(RED)✗$(NC) Functional: Sync verification failed"; \ fi; \ fi @@ -169,22 +179,18 @@ test-functional: # TEST: By SDK Type # ============================================================================ -test-sync: +test-sync: $(VENV)/.deps-installed @echo "Testing Sync SDK..." - @if [ -f "$(SYNC_DIR)/Makefile" ]; then \ - $(MAKE) -C $(SYNC_DIR) test; \ - elif [ -d "$(SYNC_DIR)/tests" ]; then \ - cd $(SYNC_DIR) && pytest tests/ -v; \ + @if [ -d "$(SYNC_DIR)/tests" ]; then \ + cd $(SYNC_DIR) && PYTHONPATH=src ../$(PYTEST) tests/ -v; \ else \ echo " $(YELLOW)⊘$(NC) Sync SDK tests not found"; \ fi -test-async: +test-async: $(VENV)/.deps-installed @echo "Testing Async SDK..." - @if [ -f "$(ASYNC_DIR)/Makefile" ]; then \ - $(MAKE) -C $(ASYNC_DIR) test; \ - elif [ -d "$(ASYNC_DIR)/tests" ]; then \ - cd $(ASYNC_DIR) && pytest tests/ -v; \ + @if [ -d "$(ASYNC_DIR)/tests" ]; then \ + cd $(ASYNC_DIR) && PYTHONPATH=src ../$(PYTEST) tests/ -v; \ else \ echo " $(YELLOW)⊘$(NC) Async SDK tests not found"; \ fi @@ -193,32 +199,21 @@ test-async: # Development # ============================================================================ -install: - @echo "Installing Python SDKs..." - @if [ -f "$(SYNC_DIR)/setup.py" ]; then cd $(SYNC_DIR) && pip install -e . ; fi - @if [ -f "$(ASYNC_DIR)/setup.py" ]; then cd $(ASYNC_DIR) && pip install -e . ; fi - @echo "$(GREEN)✓$(NC) Installation complete" - -dev-install: - @echo "Installing Python SDKs with dev dependencies..." - @if [ -f "$(SYNC_DIR)/setup.py" ]; then cd $(SYNC_DIR) && pip install -e ".[dev]" 2>/dev/null || pip install -e . ; fi - @if [ -f "$(ASYNC_DIR)/setup.py" ]; then cd $(ASYNC_DIR) && pip install -e ".[dev]" 2>/dev/null || pip install -e . ; fi - @pip install pytest pytest-cov pytest-asyncio black flake8 mypy 2>/dev/null || true - @echo "$(GREEN)✓$(NC) Dev installation complete" - -lint: +lint: $(VENV)/.deps-installed @echo "Linting Python SDKs..." - @if [ -d "$(SYNC_DIR)/src" ]; then flake8 $(SYNC_DIR)/src/ --max-line-length=120 || true; fi - @if [ -d "$(ASYNC_DIR)/src" ]; then flake8 $(ASYNC_DIR)/src/ --max-line-length=120 || true; fi + @$(VENV)/bin/pip install -q flake8 2>/dev/null || true + @if [ -d "$(SYNC_DIR)/src" ]; then $(VENV)/bin/flake8 $(SYNC_DIR)/src/ --max-line-length=120 || true; fi + @if [ -d "$(ASYNC_DIR)/src" ]; then $(VENV)/bin/flake8 $(ASYNC_DIR)/src/ --max-line-length=120 || true; fi @echo "$(GREEN)✓$(NC) Lint complete" -format: +format: $(VENV)/.deps-installed @echo "Formatting Python SDKs..." - @if [ -d "$(SYNC_DIR)/src" ]; then black $(SYNC_DIR)/src/ $(SYNC_DIR)/tests/ 2>/dev/null || true; fi - @if [ -d "$(ASYNC_DIR)/src" ]; then black $(ASYNC_DIR)/src/ $(ASYNC_DIR)/tests/ 2>/dev/null || true; fi + @$(VENV)/bin/pip install -q black 2>/dev/null || true + @if [ -d "$(SYNC_DIR)/src" ]; then $(VENV)/bin/black $(SYNC_DIR)/src/ $(SYNC_DIR)/tests/ 2>/dev/null || true; fi + @if [ -d "$(ASYNC_DIR)/src" ]; then $(VENV)/bin/black $(ASYNC_DIR)/src/ $(ASYNC_DIR)/tests/ 2>/dev/null || true; fi @echo "$(GREEN)✓$(NC) Format complete" -examples: +examples: $(VENV)/.deps-installed @echo "Running Python examples..." @if [ -f "$(ASYNC_DIR)/Makefile" ]; then $(MAKE) -C $(ASYNC_DIR) examples; fi @@ -228,6 +223,7 @@ examples: clean: @echo "Cleaning Python build artifacts..." + @rm -rf $(VENV) @find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true @find . -type f -name "*.pyc" -delete 2>/dev/null || true @find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true @@ -236,4 +232,4 @@ clean: @find . -type d -name "htmlcov" -exec rm -rf {} + 2>/dev/null || true @find . -type d -name "dist" -exec rm -rf {} + 2>/dev/null || true @find . -type d -name "build" -exec rm -rf {} + 2>/dev/null || true - @echo "$(GREEN)✓$(NC) Cleaned build artifacts" + @echo "$(GREEN)✓$(NC) Cleaned build artifacts + venv"