From daabc48d1e06b2f3b14ae20cfe7eb16cb6f4570d Mon Sep 17 00:00:00 2001 From: Steven French Date: Mon, 8 Dec 2025 11:47:37 -0500 Subject: [PATCH 01/13] Add architecture diagram and transcripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Mermaid diagram (architecture.mmd) documenting the YugaStore Java microservices architecture including API Gateway, Products, Cart, Checkout, and Login services - Add transcripts directory with workshop transcript 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- architecture.mmd | 236 ++++++++++++++++++++++ transcripts/12.08.2025_transcript_01.docx | Bin 0 -> 34230 bytes 2 files changed, 236 insertions(+) create mode 100644 architecture.mmd create mode 100644 transcripts/12.08.2025_transcript_01.docx diff --git a/architecture.mmd b/architecture.mmd new file mode 100644 index 0000000..5e22ff9 --- /dev/null +++ b/architecture.mmd @@ -0,0 +1,236 @@ +%% YugaStore Java Microservices Architecture +%% This diagram shows the component and class architecture of the application + +flowchart TB + subgraph Client["Client Layer"] + ReactUI["React UI"] + end + + subgraph Gateway["API Gateway Microservice :8081"] + direction TB + GatewayMain["YugastoreApiGateway
@SpringBootApplication"] + + subgraph GatewayControllers["Controllers"] + ProductCatalogCtrl["ProductCatalogController"] + ShoppingCartCtrl["ShoppingCartController"] + end + + subgraph GatewayServices["Services"] + ProductCatalogSvcRest["ProductCatalogServiceRest
«interface»"] + ShoppingCartSvcRest["ShoppingCartServiceRest
«interface»"] + CheckoutSvcRest["CheckoutServiceRest
«interface»"] + ProductCatalogSvcImpl["ProductCatalogServiceRestImpl"] + ShoppingCartSvcImpl["ShoppingCartServiceRestImpl"] + CheckoutSvcImpl["CheckoutServiceRestImpl"] + end + + subgraph GatewayFeignClients["Feign Clients"] + ProductFeignClient["ProductCatalogRestClient
@FeignClient"] + CartFeignClient["ShoppingCartRestClient
@FeignClient"] + CheckoutFeignClient["CheckoutRestClient
@FeignClient"] + end + + ProductCatalogSvcImpl -.->|implements| ProductCatalogSvcRest + ShoppingCartSvcImpl -.->|implements| ShoppingCartSvcRest + CheckoutSvcImpl -.->|implements| CheckoutSvcRest + + ProductCatalogCtrl --> ProductCatalogSvcRest + ShoppingCartCtrl --> ShoppingCartSvcRest + ShoppingCartCtrl --> CheckoutSvcRest + + ProductCatalogSvcImpl --> ProductFeignClient + ShoppingCartSvcImpl --> CartFeignClient + CheckoutSvcImpl --> CheckoutFeignClient + end + + subgraph Products["Products Microservice :8082"] + direction TB + ProductsMain["YugastoreProducts
@SpringBootApplication"] + + subgraph ProductControllers["Controllers"] + ProductCtrl["ProductCatalogController"] + end + + subgraph ProductServices["Services"] + ProductSvc["ProductService
«interface»"] + ProductSvcImpl["ProductServiceImpl"] + ProductInvSvc["ProductInventoryService
«interface»"] + ProductInvSvcImpl["ProductInventoryServiceImpl"] + ProductRankSvc["ProductRankingService
«interface»"] + ProductRankSvcImpl["ProductRankingServiceImpl"] + end + + subgraph ProductRepos["Repositories"] + ProductMetaRepo["ProductMetadataRepo
extends CassandraRepository"] + ProductInvRepo["ProductInventoryRepository
extends CassandraRepository"] + ProductRankRepo["ProductRankingRepository
extends CassandraRepository"] + end + + subgraph ProductDomain["Domain Models"] + ProductMetadata["ProductMetadata
@Table products"] + ProductInventory["ProductInventory
@Table product_inventory"] + ProductRanking["ProductRanking
@Table product_rankings"] + ProductRankingKey["ProductRankingKey
@PrimaryKeyClass"] + end + + ProductSvcImpl -.->|implements| ProductSvc + ProductInvSvcImpl -.->|implements| ProductInvSvc + ProductRankSvcImpl -.->|implements| ProductRankSvc + + ProductCtrl --> ProductSvc + ProductCtrl --> ProductRankSvc + + ProductSvcImpl --> ProductMetaRepo + ProductInvSvcImpl --> ProductInvRepo + ProductRankSvcImpl --> ProductRankRepo + + ProductMetaRepo --> ProductMetadata + ProductInvRepo --> ProductInventory + ProductRankRepo --> ProductRanking + ProductRanking --> ProductRankingKey + end + + subgraph Cart["Cart Microservice :8083"] + direction TB + CartMain["YugastoreCart
@SpringBootApplication"] + + subgraph CartControllers["Controllers"] + CartCtrl["ShoppingCartController"] + end + + subgraph CartServices["Services"] + CartSvcImpl["ShoppingCartImpl
@Transactional"] + end + + subgraph CartRepos["Repositories"] + CartRepo["ShoppingCartRepository
extends CrudRepository"] + end + + subgraph CartDomain["Domain Models"] + ShoppingCart["ShoppingCart
@Entity shopping_cart"] + ShoppingCartKey["ShoppingCartKey"] + end + + CartCtrl --> CartSvcImpl + CartSvcImpl --> CartRepo + CartRepo --> ShoppingCart + ShoppingCart --> ShoppingCartKey + end + + subgraph Checkout["Checkout Microservice :8086"] + direction TB + CheckoutMain["YugastoreCheckout
@SpringBootApplication"] + + subgraph CheckoutControllers["Controllers"] + CheckoutCtrl["CheckoutController"] + end + + subgraph CheckoutServices["Services"] + CheckoutSvc["CheckoutServiceImpl
@Transactional"] + end + + subgraph CheckoutRepos["Repositories"] + CheckoutInvRepo["ProductInventoryRepository
extends CassandraRepository"] + end + + subgraph CheckoutFeignClients["Feign Clients"] + CheckoutProductClient["ProductCatalogRestClient
@FeignClient"] + CheckoutCartClient["ShoppingCartRestClient
@FeignClient"] + end + + subgraph CheckoutDomain["Domain Models"] + Order["Order"] + CheckoutStatus["CheckoutStatus"] + end + + CheckoutCtrl --> CheckoutSvc + CheckoutSvc --> CheckoutInvRepo + CheckoutSvc --> CheckoutProductClient + CheckoutSvc --> CheckoutCartClient + CheckoutSvc --> Order + CheckoutSvc --> CheckoutStatus + end + + subgraph Login["Login Microservice :8085"] + direction TB + LoginMain["YugastoreLoginService
@SpringBootApplication"] + + subgraph LoginControllers["Controllers"] + UserCtrl["UserController"] + end + + subgraph LoginServices["Services"] + UserSvc["UserService
«interface»"] + UserSvcImpl["UserServiceImpl"] + SecuritySvc["SecurityService
«interface»"] + SecuritySvcImpl["SecurityServiceImpl"] + UserDetailsSvc["UserDetailsServiceImpl"] + end + + subgraph LoginRepos["Repositories"] + UserRepo["UserRepository
extends JpaRepository"] + RoleRepo["RoleRepository
extends JpaRepository"] + end + + subgraph LoginDomain["Domain Models"] + User["User
@Entity username"] + Role["Role
@Entity role"] + end + + UserSvcImpl -.->|implements| UserSvc + SecuritySvcImpl -.->|implements| SecuritySvc + + UserCtrl --> UserSvc + UserSvcImpl --> UserRepo + UserDetailsSvc --> UserRepo + UserRepo --> User + RoleRepo --> Role + User -->|ManyToMany| Role + end + + subgraph Databases["Data Layer"] + direction LR + subgraph Cassandra["YugabyteDB / Cassandra :9042"] + CassandraDB[("Keyspace: cronos
- products
- product_inventory
- product_rankings")] + end + + subgraph Postgres["PostgreSQL :5433"] + PostgresDB[("- shopping_cart
- username
- role")] + end + end + + %% Client connections + ReactUI --> Gateway + + %% Feign client connections (service-to-service) + ProductFeignClient -->|REST| Products + CartFeignClient -->|REST| Cart + CheckoutFeignClient -->|REST| Checkout + CheckoutProductClient -->|REST| Products + CheckoutCartClient -->|REST| Cart + + %% Database connections + ProductMetaRepo -->|YCQL| CassandraDB + ProductInvRepo -->|YCQL| CassandraDB + ProductRankRepo -->|YCQL| CassandraDB + CheckoutInvRepo -->|YCQL| CassandraDB + CartRepo -->|JPA| PostgresDB + UserRepo -->|JPA| PostgresDB + RoleRepo -->|JPA| PostgresDB + + %% Styling + classDef controller fill:#a8d5ba,stroke:#2d6a4f + classDef service fill:#b8d4e3,stroke:#1d3557 + classDef repo fill:#f4d35e,stroke:#ee964b + classDef domain fill:#f7b267,stroke:#f25c54 + classDef feign fill:#d4a5a5,stroke:#9c6644 + classDef database fill:#c8b6ff,stroke:#7b2cbf + classDef main fill:#90be6d,stroke:#43aa8b + + class ProductCatalogCtrl,ShoppingCartCtrl,ProductCtrl,CartCtrl,CheckoutCtrl,UserCtrl controller + class ProductCatalogSvcRest,ShoppingCartSvcRest,CheckoutSvcRest,ProductCatalogSvcImpl,ShoppingCartSvcImpl,CheckoutSvcImpl,ProductSvc,ProductSvcImpl,ProductInvSvc,ProductInvSvcImpl,ProductRankSvc,ProductRankSvcImpl,CartSvcImpl,CheckoutSvc,UserSvc,UserSvcImpl,SecuritySvc,SecuritySvcImpl,UserDetailsSvc service + class ProductMetaRepo,ProductInvRepo,ProductRankRepo,CartRepo,CheckoutInvRepo,UserRepo,RoleRepo repo + class ProductMetadata,ProductInventory,ProductRanking,ProductRankingKey,ShoppingCart,ShoppingCartKey,Order,CheckoutStatus,User,Role domain + class ProductFeignClient,CartFeignClient,CheckoutFeignClient,CheckoutProductClient,CheckoutCartClient feign + class CassandraDB,PostgresDB database + class GatewayMain,ProductsMain,CartMain,CheckoutMain,LoginMain main diff --git a/transcripts/12.08.2025_transcript_01.docx b/transcripts/12.08.2025_transcript_01.docx new file mode 100644 index 0000000000000000000000000000000000000000..694e35721fd3ab26a51f49cac352c737ea6331ff GIT binary patch literal 34230 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�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

gtxcJFaHV26Pr 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 Rm;g+`BV-E7^Z6sj{{bfgWPAVs literal 0 HcmV?d00001 From 14dece6b075d639270619dcca9f47cbdde9d97f4 Mon Sep 17 00:00:00 2001 From: Steven French Date: Mon, 8 Dec 2025 12:12:36 -0500 Subject: [PATCH 02/13] Add bootstrap.sh script to automate app startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created bootstrap.sh in the root folder. The script: 1. Builds the application with `mvn -DskipTests package` 2. Initializes YugabyteDB - creates CQL schema and loads sample data 3. Creates YSQL tables for PostgreSQL 4. Starts all microservices in background: - Eureka Service Discovery (port 8761) - API Gateway (port 8081) - Products (port 8082) - Checkout (port 8086) - Cart (port 8083) - React UI (port 8080) Logging features: - All output goes to bootstrap.log - Each command logs: timestamp, command, directory, and status - On failure: logs exit code with human-readable description - Error output is captured and logged - Individual service logs go to *.out files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- bootstrap.sh | 172 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100755 bootstrap.sh diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..246150c --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,172 @@ +#!/bin/bash + +# Bootstrap script for Yugastore Java application +# This script follows the "Running the app on host" instructions from README.md + +# Configuration +LOG_FILE="bootstrap.log" +BASE_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Initialize log file +echo "=== Yugastore Bootstrap Log ===" > "$LOG_FILE" +echo "Started at: $(date)" >> "$LOG_FILE" +echo "Working directory: $BASE_DIR" >> "$LOG_FILE" +echo "" >> "$LOG_FILE" + +# Function to map common exit codes to descriptions +get_exit_code_description() { + local code=$1 + case $code in + 0) echo "Success" ;; + 1) echo "General error" ;; + 2) echo "Misuse of shell command" ;; + 126) echo "Command invoked cannot execute (permission problem or not executable)" ;; + 127) echo "Command not found" ;; + 128) echo "Invalid argument to exit" ;; + 130) echo "Script terminated by Ctrl+C" ;; + 137) echo "Process killed (SIGKILL)" ;; + 139) echo "Segmentation fault (SIGSEGV)" ;; + 143) echo "Process terminated (SIGTERM)" ;; + 255) echo "Exit status out of range" ;; + *) echo "Unknown error code" ;; + esac +} + +# Function to run a command with logging +run_command() { + local description="$1" + local command="$2" + local working_dir="${3:-$BASE_DIR}" + + echo "[$(date '+%Y-%m-%d %H:%M:%S')] RUNNING: $description" >> "$LOG_FILE" + echo " Command: $command" >> "$LOG_FILE" + echo " Directory: $working_dir" >> "$LOG_FILE" + + # Run command and capture output and exit code + cd "$working_dir" + output=$(eval "$command" 2>&1) + exit_code=$? + + if [ $exit_code -eq 0 ]; then + echo " Status: SUCCESS" >> "$LOG_FILE" + else + local error_desc=$(get_exit_code_description $exit_code) + echo " Status: FAILURE" >> "$LOG_FILE" + echo " Exit Code: $exit_code ($error_desc)" >> "$LOG_FILE" + echo " Error Output: $output" >> "$LOG_FILE" + fi + echo "" >> "$LOG_FILE" + + cd "$BASE_DIR" + return $exit_code +} + +# Function to run a background service with logging +run_service_background() { + local description="$1" + local command="$2" + local working_dir="${3:-$BASE_DIR}" + local log_prefix="$4" + + echo "[$(date '+%Y-%m-%d %H:%M:%S')] STARTING SERVICE: $description" >> "$LOG_FILE" + echo " Command: $command" >> "$LOG_FILE" + echo " Directory: $working_dir" >> "$LOG_FILE" + + cd "$working_dir" + nohup $command > "${BASE_DIR}/${log_prefix}.out" 2>&1 & + local pid=$! + + # Give the service a moment to start or fail immediately + sleep 3 + + if ps -p $pid > /dev/null 2>&1; then + echo " Status: SUCCESS (PID: $pid)" >> "$LOG_FILE" + echo " Service output logged to: ${log_prefix}.out" >> "$LOG_FILE" + else + wait $pid 2>/dev/null + exit_code=$? + local error_desc=$(get_exit_code_description $exit_code) + echo " Status: FAILURE" >> "$LOG_FILE" + echo " Exit Code: $exit_code ($error_desc)" >> "$LOG_FILE" + echo " Error Output: $(tail -20 ${BASE_DIR}/${log_prefix}.out 2>/dev/null)" >> "$LOG_FILE" + fi + echo "" >> "$LOG_FILE" + + cd "$BASE_DIR" +} + +echo "Starting Yugastore bootstrap..." +echo "Log file: $LOG_FILE" +echo "" + +# Build the application +echo "Building the application..." +run_command "Build application with Maven" "mvn -DskipTests package" +if [ $? -ne 0 ]; then + echo "ERROR: Build failed. Check $LOG_FILE for details." + exit 1 +fi + +# Step 1: Initialize YugabyteDB - Create CQL schema +echo "Step 1: Initializing YugabyteDB..." +run_command "Create CQL schema" "cqlsh -f schema.cql" "$BASE_DIR/resources" +if [ $? -ne 0 ]; then + echo "WARNING: CQL schema creation failed. Is YugabyteDB running? Check $LOG_FILE for details." +fi + +# Step 1: Load sample data +echo "Loading sample data..." +run_command "Load sample data" "./dataload.sh" "$BASE_DIR/resources" +if [ $? -ne 0 ]; then + echo "WARNING: Data load failed. Check $LOG_FILE for details." +fi + +# Step 1: Create PostgreSQL/YSQL tables +echo "Creating YSQL tables..." +run_command "Create YSQL schema" "psql -h localhost -p 5433 -U yugabyte -d yugabyte -f schema.sql" "$BASE_DIR/resources" +if [ $? -ne 0 ]; then + echo "WARNING: YSQL schema creation failed. Check $LOG_FILE for details." +fi + +# Step 2: Start Eureka service discovery +echo "Step 2: Starting Eureka service discovery..." +run_service_background "Eureka Service Discovery" "mvn spring-boot:run" "$BASE_DIR/eureka-server-local" "eureka-server" +echo "Waiting for Eureka to initialize (30 seconds)..." +sleep 30 + +# Step 2 (continued): Start API Gateway microservice +echo "Starting API Gateway microservice..." +run_service_background "API Gateway Microservice" "mvn spring-boot:run" "$BASE_DIR/api-gateway-microservice" "api-gateway" +sleep 10 + +# Step 3: Start Products microservice +echo "Step 3: Starting Products microservice..." +run_service_background "Products Microservice" "mvn spring-boot:run" "$BASE_DIR/products-microservice" "products" +sleep 10 + +# Step 4: Start Checkout microservice +echo "Step 4: Starting Checkout microservice..." +run_service_background "Checkout Microservice" "mvn spring-boot:run" "$BASE_DIR/checkout-microservice" "checkout" +sleep 10 + +# Step 5: Start Cart microservice +echo "Step 5: Starting Cart microservice..." +run_service_background "Cart Microservice" "mvn spring-boot:run" "$BASE_DIR/cart-microservice" "cart" +sleep 10 + +# Step 6: Start the React UI +echo "Step 6: Starting React UI..." +run_service_background "React UI" "mvn spring-boot:run" "$BASE_DIR/react-ui" "react-ui" + +echo "" +echo "=== Bootstrap Complete ===" | tee -a "$LOG_FILE" +echo "Completed at: $(date)" >> "$LOG_FILE" +echo "" +echo "Services should be available at:" +echo " - Eureka Dashboard: http://localhost:8761/" +echo " - Marketplace App: http://localhost:8080/" +echo "" +echo "Check $LOG_FILE for detailed execution log." +echo "Check individual service logs (*.out files) for service output." +echo "" +echo "To stop all services, run: pkill -f 'spring-boot:run'" From f9e815506f3e0e77c940889b455d7278a16614bf Mon Sep 17 00:00:00 2001 From: Steven French Date: Mon, 8 Dec 2025 13:04:06 -0500 Subject: [PATCH 03/13] Reorganize bootstrap script and add test suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move bootstrap.sh from root to scripts/ directory - Fix Maven build failure by adding -Dexec.skip=true flag to skip Docker image builds when running in native YugabyteDB mode - Add comprehensive BATS test suite (65 tests) covering: - Help/usage flags (-h, --help) - Argument parsing and error handling - Script structure and required functions - Prerequisite checks (java, mvn, python3, cqlsh, psql) - Exit code mapping - YugabyteDB docker and native installation modes - Microservice startup configuration - Port configuration for all services - Add scripts/tests/README.md with BATS installation and usage guide - Update main README.md with: - Quick start section using bootstrap script - Table of prerequisites requiring manual intervention - Bootstrap script options documentation - Instructions to stop services 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- README.md | 71 ++- bootstrap.sh | 172 ------ scripts/bootstrap.sh | 923 ++++++++++++++++++++++++++++++ scripts/tests/README.md | 151 +++++ scripts/tests/bootstrap_test.bats | 427 ++++++++++++++ 5 files changed, 1570 insertions(+), 174 deletions(-) delete mode 100755 bootstrap.sh create mode 100755 scripts/bootstrap.sh create mode 100644 scripts/tests/README.md create mode 100755 scripts/tests/bootstrap_test.bats diff --git a/README.md b/README.md index e5ef260..3a037a1 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,74 @@ The architecture diagram of Yugastore is shown below. # Build and run -To build, simply run the following from the base directory: +There are two ways to build and run the application: +1. **Automated Bootstrap Script** (Recommended) - Single command to set up everything +2. **Manual Steps** - Step-by-step instructions for more control + +## Quick Start with Bootstrap Script + +The easiest way to get started is using the automated bootstrap script, which handles all prerequisites, builds the application, and starts all services. + +```bash +# Using native YugabyteDB installation (recommended for development) +./scripts/bootstrap.sh --yugabyte=native + +# Or using Docker for YugabyteDB +./scripts/bootstrap.sh --yugabyte=docker + +# For help and all options +./scripts/bootstrap.sh --help +``` + +### Prerequisites Requiring Manual Intervention + +The bootstrap script will attempt to install missing prerequisites automatically, but the following may require manual steps: + +| Prerequisite | Platform | Manual Action Required | +|--------------|----------|----------------------| +| **Homebrew** | macOS | Install from [brew.sh](https://brew.sh) if not present | +| **Docker Desktop** | macOS/Windows | Must be installed manually from [docker.com](https://www.docker.com/products/docker-desktop/) | +| **Port 7000 conflict** | macOS | Disable AirPlay Receiver in System Settings > General > AirDrop & Handoff, or YugabyteDB will use alternate port 7001 | +| **WSL** | Windows | The script requires WSL (Windows Subsystem for Linux) to run on Windows | + +### Bootstrap Script Options + +| Option | Description | +|--------|-------------| +| `--non-interactive` | Run without prompts (assumes YugabyteDB is ready) | +| `--yugabyte=native` | Install YugabyteDB via native package manager (Homebrew on macOS) | +| `--yugabyte=docker` | Run YugabyteDB in Docker container (default) | +| `--help`, `-h` | Show help message | + +### What the Bootstrap Script Does + +1. Checks and installs prerequisites (Java 17, Maven, Python 3, cqlsh, psql) +2. Installs and starts YugabyteDB (native or Docker based on option) +3. Builds all microservices with Maven +4. Creates database schemas (CQL and SQL) +5. Loads sample product data +6. Starts all 6 microservices in the background +7. Provides URLs for all running services + +### Stopping Services + +To stop all running microservices: + +```bash +pkill -f 'spring-boot:run' +``` + +To stop YugabyteDB (native installation): + +```bash +yugabyted stop +``` + +--- + +## Manual Build and Run + +To build manually, run the following from the base directory: ``` $ mvn -DskipTests package @@ -54,7 +121,7 @@ $ mvn -DskipTests package To run the app on host machine, you need to first install YugabyteDB, create the necessary tables, start each of the microservices and finally the React UI. -## Running the app on host +### Running the app on host (Manual Steps) Make sure you have built the app as described above. Now do the following steps. diff --git a/bootstrap.sh b/bootstrap.sh deleted file mode 100755 index 246150c..0000000 --- a/bootstrap.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/bin/bash - -# Bootstrap script for Yugastore Java application -# This script follows the "Running the app on host" instructions from README.md - -# Configuration -LOG_FILE="bootstrap.log" -BASE_DIR="$(cd "$(dirname "$0")" && pwd)" - -# Initialize log file -echo "=== Yugastore Bootstrap Log ===" > "$LOG_FILE" -echo "Started at: $(date)" >> "$LOG_FILE" -echo "Working directory: $BASE_DIR" >> "$LOG_FILE" -echo "" >> "$LOG_FILE" - -# Function to map common exit codes to descriptions -get_exit_code_description() { - local code=$1 - case $code in - 0) echo "Success" ;; - 1) echo "General error" ;; - 2) echo "Misuse of shell command" ;; - 126) echo "Command invoked cannot execute (permission problem or not executable)" ;; - 127) echo "Command not found" ;; - 128) echo "Invalid argument to exit" ;; - 130) echo "Script terminated by Ctrl+C" ;; - 137) echo "Process killed (SIGKILL)" ;; - 139) echo "Segmentation fault (SIGSEGV)" ;; - 143) echo "Process terminated (SIGTERM)" ;; - 255) echo "Exit status out of range" ;; - *) echo "Unknown error code" ;; - esac -} - -# Function to run a command with logging -run_command() { - local description="$1" - local command="$2" - local working_dir="${3:-$BASE_DIR}" - - echo "[$(date '+%Y-%m-%d %H:%M:%S')] RUNNING: $description" >> "$LOG_FILE" - echo " Command: $command" >> "$LOG_FILE" - echo " Directory: $working_dir" >> "$LOG_FILE" - - # Run command and capture output and exit code - cd "$working_dir" - output=$(eval "$command" 2>&1) - exit_code=$? - - if [ $exit_code -eq 0 ]; then - echo " Status: SUCCESS" >> "$LOG_FILE" - else - local error_desc=$(get_exit_code_description $exit_code) - echo " Status: FAILURE" >> "$LOG_FILE" - echo " Exit Code: $exit_code ($error_desc)" >> "$LOG_FILE" - echo " Error Output: $output" >> "$LOG_FILE" - fi - echo "" >> "$LOG_FILE" - - cd "$BASE_DIR" - return $exit_code -} - -# Function to run a background service with logging -run_service_background() { - local description="$1" - local command="$2" - local working_dir="${3:-$BASE_DIR}" - local log_prefix="$4" - - echo "[$(date '+%Y-%m-%d %H:%M:%S')] STARTING SERVICE: $description" >> "$LOG_FILE" - echo " Command: $command" >> "$LOG_FILE" - echo " Directory: $working_dir" >> "$LOG_FILE" - - cd "$working_dir" - nohup $command > "${BASE_DIR}/${log_prefix}.out" 2>&1 & - local pid=$! - - # Give the service a moment to start or fail immediately - sleep 3 - - if ps -p $pid > /dev/null 2>&1; then - echo " Status: SUCCESS (PID: $pid)" >> "$LOG_FILE" - echo " Service output logged to: ${log_prefix}.out" >> "$LOG_FILE" - else - wait $pid 2>/dev/null - exit_code=$? - local error_desc=$(get_exit_code_description $exit_code) - echo " Status: FAILURE" >> "$LOG_FILE" - echo " Exit Code: $exit_code ($error_desc)" >> "$LOG_FILE" - echo " Error Output: $(tail -20 ${BASE_DIR}/${log_prefix}.out 2>/dev/null)" >> "$LOG_FILE" - fi - echo "" >> "$LOG_FILE" - - cd "$BASE_DIR" -} - -echo "Starting Yugastore bootstrap..." -echo "Log file: $LOG_FILE" -echo "" - -# Build the application -echo "Building the application..." -run_command "Build application with Maven" "mvn -DskipTests package" -if [ $? -ne 0 ]; then - echo "ERROR: Build failed. Check $LOG_FILE for details." - exit 1 -fi - -# Step 1: Initialize YugabyteDB - Create CQL schema -echo "Step 1: Initializing YugabyteDB..." -run_command "Create CQL schema" "cqlsh -f schema.cql" "$BASE_DIR/resources" -if [ $? -ne 0 ]; then - echo "WARNING: CQL schema creation failed. Is YugabyteDB running? Check $LOG_FILE for details." -fi - -# Step 1: Load sample data -echo "Loading sample data..." -run_command "Load sample data" "./dataload.sh" "$BASE_DIR/resources" -if [ $? -ne 0 ]; then - echo "WARNING: Data load failed. Check $LOG_FILE for details." -fi - -# Step 1: Create PostgreSQL/YSQL tables -echo "Creating YSQL tables..." -run_command "Create YSQL schema" "psql -h localhost -p 5433 -U yugabyte -d yugabyte -f schema.sql" "$BASE_DIR/resources" -if [ $? -ne 0 ]; then - echo "WARNING: YSQL schema creation failed. Check $LOG_FILE for details." -fi - -# Step 2: Start Eureka service discovery -echo "Step 2: Starting Eureka service discovery..." -run_service_background "Eureka Service Discovery" "mvn spring-boot:run" "$BASE_DIR/eureka-server-local" "eureka-server" -echo "Waiting for Eureka to initialize (30 seconds)..." -sleep 30 - -# Step 2 (continued): Start API Gateway microservice -echo "Starting API Gateway microservice..." -run_service_background "API Gateway Microservice" "mvn spring-boot:run" "$BASE_DIR/api-gateway-microservice" "api-gateway" -sleep 10 - -# Step 3: Start Products microservice -echo "Step 3: Starting Products microservice..." -run_service_background "Products Microservice" "mvn spring-boot:run" "$BASE_DIR/products-microservice" "products" -sleep 10 - -# Step 4: Start Checkout microservice -echo "Step 4: Starting Checkout microservice..." -run_service_background "Checkout Microservice" "mvn spring-boot:run" "$BASE_DIR/checkout-microservice" "checkout" -sleep 10 - -# Step 5: Start Cart microservice -echo "Step 5: Starting Cart microservice..." -run_service_background "Cart Microservice" "mvn spring-boot:run" "$BASE_DIR/cart-microservice" "cart" -sleep 10 - -# Step 6: Start the React UI -echo "Step 6: Starting React UI..." -run_service_background "React UI" "mvn spring-boot:run" "$BASE_DIR/react-ui" "react-ui" - -echo "" -echo "=== Bootstrap Complete ===" | tee -a "$LOG_FILE" -echo "Completed at: $(date)" >> "$LOG_FILE" -echo "" -echo "Services should be available at:" -echo " - Eureka Dashboard: http://localhost:8761/" -echo " - Marketplace App: http://localhost:8080/" -echo "" -echo "Check $LOG_FILE for detailed execution log." -echo "Check individual service logs (*.out files) for service output." -echo "" -echo "To stop all services, run: pkill -f 'spring-boot:run'" diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..cce1428 --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,923 @@ +#!/bin/bash + +# Bootstrap script for Yugastore Java application +# This script follows the "Build and run" and "Running the app on host" instructions from README.md +# +# Usage: +# ./bootstrap.sh # Interactive mode, assumes Docker YugabyteDB +# ./bootstrap.sh --non-interactive # Non-interactive mode, assumes Docker YugabyteDB already running +# ./bootstrap.sh --yugabyte=docker # Use Docker for YugabyteDB (install if needed) +# ./bootstrap.sh --yugabyte=native # Use native install for YugabyteDB (install if needed) +# ./bootstrap.sh --help # Show help + +# Configuration +LOG_FILE="bootstrap.log" +BASE_DIR="$(cd "$(dirname "$0")" && pwd)" +MISSING_PREREQS=() +INTERACTIVE=true +YUGABYTE_MODE="docker" # Default: docker + +# ============================================================================ +# PARSE COMMAND LINE ARGUMENTS +# ============================================================================ +show_help() { + echo "Yugastore Bootstrap Script" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --non-interactive Run without prompts (assumes YugabyteDB is ready)" + echo " --yugabyte=docker Use Docker to run YugabyteDB (default)" + echo " --yugabyte=native Use native package manager to install YugabyteDB" + echo " --help Show this help message" + echo "" + echo "Examples:" + echo " $0 # Interactive mode with Docker YugabyteDB" + echo " $0 --non-interactive # Non-interactive, assumes Docker YugabyteDB running" + echo " $0 --yugabyte=native # Install YugabyteDB via native package manager" + echo "" + echo "Supported Operating Systems:" + echo " - macOS (Homebrew)" + echo " - Linux (apt, yum, dnf)" + echo " - Windows (WSL required for this script)" + echo "" +} + +for arg in "$@"; do + case $arg in + --non-interactive) + INTERACTIVE=false + shift + ;; + --yugabyte=docker) + YUGABYTE_MODE="docker" + shift + ;; + --yugabyte=native) + YUGABYTE_MODE="native" + shift + ;; + --help|-h) + show_help + exit 0 + ;; + *) + echo "Unknown option: $arg" + show_help + exit 1 + ;; + esac +done + +# ============================================================================ +# DETECT OPERATING SYSTEM +# ============================================================================ +detect_os() { + case "$(uname -s)" in + Darwin*) + OS="macos" + ;; + Linux*) + # Detect Linux distribution + if [ -f /etc/os-release ]; then + . /etc/os-release + case "$ID" in + ubuntu|debian|linuxmint|pop) + OS="linux-debian" + ;; + fedora|rhel|centos|rocky|almalinux) + OS="linux-redhat" + ;; + arch|manjaro) + OS="linux-arch" + ;; + *) + OS="linux-unknown" + ;; + esac + else + OS="linux-unknown" + fi + ;; + CYGWIN*|MINGW*|MSYS*) + OS="windows" + ;; + *) + OS="unknown" + ;; + esac + echo "$OS" +} + +OS_TYPE=$(detect_os) + +# Initialize log file +echo "=== Yugastore Bootstrap Log ===" > "$LOG_FILE" +echo "Started at: $(date)" >> "$LOG_FILE" +echo "Working directory: $BASE_DIR" >> "$LOG_FILE" +echo "Operating System: $OS_TYPE" >> "$LOG_FILE" +echo "Interactive Mode: $INTERACTIVE" >> "$LOG_FILE" +echo "YugabyteDB Mode: $YUGABYTE_MODE" >> "$LOG_FILE" +echo "" >> "$LOG_FILE" + +# Function to map common exit codes to descriptions +get_exit_code_description() { + local code=$1 + case $code in + 0) echo "Success" ;; + 1) echo "General error" ;; + 2) echo "Misuse of shell command" ;; + 126) echo "Command invoked cannot execute (permission problem or not executable)" ;; + 127) echo "Command not found" ;; + 128) echo "Invalid argument to exit" ;; + 130) echo "Script terminated by Ctrl+C" ;; + 137) echo "Process killed (SIGKILL)" ;; + 139) echo "Segmentation fault (SIGSEGV)" ;; + 143) echo "Process terminated (SIGTERM)" ;; + 255) echo "Exit status out of range" ;; + *) echo "Unknown error code" ;; + esac +} + +# Function to log a message +log_message() { + local level="$1" + local message="$2" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" >> "$LOG_FILE" + if [ "$level" = "ERROR" ] || [ "$level" = "WARNING" ]; then + echo "[$level] $message" + fi +} + +# Function to check if a command exists +check_command() { + local cmd="$1" + local install_hint="$2" + + if command -v "$cmd" &> /dev/null; then + log_message "INFO" "Prerequisite check: $cmd found at $(which $cmd)" + return 0 + else + log_message "ERROR" "Prerequisite check: $cmd NOT FOUND. Install with: $install_hint" + MISSING_PREREQS+=("$cmd (install with: $install_hint)") + return 1 + fi +} + +# ============================================================================ +# OS-SPECIFIC PACKAGE INSTALLATION FUNCTIONS +# ============================================================================ + +# Install package based on OS +install_package() { + local package_name="$1" + local macos_package="${2:-$1}" + local debian_package="${3:-$1}" + local redhat_package="${4:-$1}" + + log_message "INFO" "Attempting to install $package_name for $OS_TYPE..." + echo "Installing $package_name..." + + case "$OS_TYPE" in + macos) + if command -v brew &> /dev/null; then + brew install "$macos_package" 2>&1 | tee -a "$LOG_FILE" + return ${PIPESTATUS[0]} + else + log_message "ERROR" "Homebrew not found. Please install Homebrew first." + return 1 + fi + ;; + linux-debian) + if command -v apt-get &> /dev/null; then + sudo apt-get update && sudo apt-get install -y "$debian_package" 2>&1 | tee -a "$LOG_FILE" + return ${PIPESTATUS[0]} + else + log_message "ERROR" "apt-get not found." + return 1 + fi + ;; + linux-redhat) + if command -v dnf &> /dev/null; then + sudo dnf install -y "$redhat_package" 2>&1 | tee -a "$LOG_FILE" + return ${PIPESTATUS[0]} + elif command -v yum &> /dev/null; then + sudo yum install -y "$redhat_package" 2>&1 | tee -a "$LOG_FILE" + return ${PIPESTATUS[0]} + else + log_message "ERROR" "Neither dnf nor yum found." + return 1 + fi + ;; + linux-arch) + if command -v pacman &> /dev/null; then + sudo pacman -S --noconfirm "$debian_package" 2>&1 | tee -a "$LOG_FILE" + return ${PIPESTATUS[0]} + else + log_message "ERROR" "pacman not found." + return 1 + fi + ;; + windows) + if command -v choco &> /dev/null; then + choco install -y "$package_name" 2>&1 | tee -a "$LOG_FILE" + return ${PIPESTATUS[0]} + elif command -v winget &> /dev/null; then + winget install --accept-package-agreements --accept-source-agreements "$package_name" 2>&1 | tee -a "$LOG_FILE" + return ${PIPESTATUS[0]} + else + log_message "ERROR" "Neither Chocolatey nor winget found. Please install packages manually." + return 1 + fi + ;; + *) + log_message "ERROR" "Unsupported OS: $OS_TYPE" + return 1 + ;; + esac +} + +# Install Java +install_java() { + log_message "INFO" "Installing Java..." + case "$OS_TYPE" in + macos) + brew install openjdk@17 2>&1 | tee -a "$LOG_FILE" + export PATH="/opt/homebrew/opt/openjdk@17/bin:$PATH" + ;; + linux-debian) + sudo apt-get update && sudo apt-get install -y openjdk-17-jdk 2>&1 | tee -a "$LOG_FILE" + ;; + linux-redhat) + sudo dnf install -y java-17-openjdk-devel 2>&1 | tee -a "$LOG_FILE" || \ + sudo yum install -y java-17-openjdk-devel 2>&1 | tee -a "$LOG_FILE" + ;; + linux-arch) + sudo pacman -S --noconfirm jdk17-openjdk 2>&1 | tee -a "$LOG_FILE" + ;; + windows) + choco install -y openjdk17 2>&1 | tee -a "$LOG_FILE" || \ + winget install --accept-package-agreements Microsoft.OpenJDK.17 2>&1 | tee -a "$LOG_FILE" + ;; + esac +} + +# Install Maven +install_maven() { + log_message "INFO" "Installing Maven..." + case "$OS_TYPE" in + macos) + brew install maven 2>&1 | tee -a "$LOG_FILE" + ;; + linux-debian) + sudo apt-get update && sudo apt-get install -y maven 2>&1 | tee -a "$LOG_FILE" + ;; + linux-redhat) + sudo dnf install -y maven 2>&1 | tee -a "$LOG_FILE" || \ + sudo yum install -y maven 2>&1 | tee -a "$LOG_FILE" + ;; + linux-arch) + sudo pacman -S --noconfirm maven 2>&1 | tee -a "$LOG_FILE" + ;; + windows) + choco install -y maven 2>&1 | tee -a "$LOG_FILE" || \ + winget install --accept-package-agreements Apache.Maven 2>&1 | tee -a "$LOG_FILE" + ;; + esac +} + +# Install Python 3 +install_python() { + log_message "INFO" "Installing Python 3..." + case "$OS_TYPE" in + macos) + brew install python@3 2>&1 | tee -a "$LOG_FILE" + ;; + linux-debian) + sudo apt-get update && sudo apt-get install -y python3 python3-pip 2>&1 | tee -a "$LOG_FILE" + ;; + linux-redhat) + sudo dnf install -y python3 python3-pip 2>&1 | tee -a "$LOG_FILE" || \ + sudo yum install -y python3 python3-pip 2>&1 | tee -a "$LOG_FILE" + ;; + linux-arch) + sudo pacman -S --noconfirm python python-pip 2>&1 | tee -a "$LOG_FILE" + ;; + windows) + choco install -y python3 2>&1 | tee -a "$LOG_FILE" || \ + winget install --accept-package-agreements Python.Python.3.11 2>&1 | tee -a "$LOG_FILE" + ;; + esac +} + +# Install psql client +install_psql() { + log_message "INFO" "Installing PostgreSQL client..." + case "$OS_TYPE" in + macos) + brew install libpq 2>&1 | tee -a "$LOG_FILE" + export PATH="/opt/homebrew/opt/libpq/bin:$PATH" + ;; + linux-debian) + sudo apt-get update && sudo apt-get install -y postgresql-client 2>&1 | tee -a "$LOG_FILE" + ;; + linux-redhat) + sudo dnf install -y postgresql 2>&1 | tee -a "$LOG_FILE" || \ + sudo yum install -y postgresql 2>&1 | tee -a "$LOG_FILE" + ;; + linux-arch) + sudo pacman -S --noconfirm postgresql-libs 2>&1 | tee -a "$LOG_FILE" + ;; + windows) + choco install -y postgresql 2>&1 | tee -a "$LOG_FILE" + ;; + esac +} + +# Install Docker +install_docker() { + log_message "INFO" "Installing Docker..." + case "$OS_TYPE" in + macos) + echo "Please install Docker Desktop for macOS from https://www.docker.com/products/docker-desktop" + echo "After installation, start Docker Desktop and re-run this script." + log_message "ERROR" "Docker Desktop must be installed manually on macOS" + return 1 + ;; + linux-debian) + sudo apt-get update + sudo apt-get install -y docker.io 2>&1 | tee -a "$LOG_FILE" + sudo systemctl start docker + sudo systemctl enable docker + sudo usermod -aG docker $USER + ;; + linux-redhat) + sudo dnf install -y docker 2>&1 | tee -a "$LOG_FILE" || \ + sudo yum install -y docker 2>&1 | tee -a "$LOG_FILE" + sudo systemctl start docker + sudo systemctl enable docker + sudo usermod -aG docker $USER + ;; + linux-arch) + sudo pacman -S --noconfirm docker 2>&1 | tee -a "$LOG_FILE" + sudo systemctl start docker + sudo systemctl enable docker + sudo usermod -aG docker $USER + ;; + windows) + echo "Please install Docker Desktop for Windows from https://www.docker.com/products/docker-desktop" + echo "After installation, start Docker Desktop and re-run this script." + log_message "ERROR" "Docker Desktop must be installed manually on Windows" + return 1 + ;; + esac +} + +# ============================================================================ +# YUGABYTEDB INSTALLATION FUNCTIONS +# ============================================================================ + +# Install YugabyteDB via Docker +install_yugabyte_docker() { + log_message "INFO" "Setting up YugabyteDB via Docker..." + echo "Setting up YugabyteDB via Docker..." + + # Check if Docker is available + if ! command -v docker &> /dev/null; then + log_message "WARNING" "Docker not found. Attempting to install..." + install_docker + if ! command -v docker &> /dev/null; then + log_message "ERROR" "Docker installation failed or requires manual intervention." + echo "ERROR: Docker is required for --yugabyte=docker mode." + echo "Please install Docker and re-run this script." + return 1 + fi + fi + + # Check if Docker daemon is running + if ! docker info &> /dev/null; then + log_message "ERROR" "Docker daemon is not running." + echo "ERROR: Docker daemon is not running. Please start Docker and re-run this script." + return 1 + fi + + # Check if yugabyte container already exists + if docker ps -a --format '{{.Names}}' | grep -q '^yugabyte$'; then + # Check if it's running + if docker ps --format '{{.Names}}' | grep -q '^yugabyte$'; then + log_message "INFO" "YugabyteDB container is already running." + echo "YugabyteDB container is already running." + return 0 + else + # Start existing container + log_message "INFO" "Starting existing YugabyteDB container..." + echo "Starting existing YugabyteDB container..." + docker start yugabyte 2>&1 | tee -a "$LOG_FILE" + sleep 10 + return 0 + fi + fi + + # Run new YugabyteDB container + log_message "INFO" "Starting new YugabyteDB Docker container..." + echo "Starting new YugabyteDB Docker container..." + docker run -d --name yugabyte \ + -p7000:7000 -p9000:9000 -p5433:5433 -p9042:9042 \ + yugabytedb/yugabyte:latest \ + bin/yugabyted start --daemon=false 2>&1 | tee -a "$LOG_FILE" + + if [ $? -ne 0 ]; then + log_message "ERROR" "Failed to start YugabyteDB Docker container." + return 1 + fi + + # Wait for YugabyteDB to be ready + echo "Waiting for YugabyteDB to initialize (30 seconds)..." + sleep 30 + + log_message "INFO" "YugabyteDB Docker container started successfully." + return 0 +} + +# Install YugabyteDB natively based on OS +install_yugabyte_native() { + log_message "INFO" "Installing YugabyteDB natively for $OS_TYPE..." + echo "Installing YugabyteDB natively..." + + case "$OS_TYPE" in + macos) + echo "Installing YugabyteDB via Homebrew..." + brew tap yugabyte/yugabytedb 2>&1 | tee -a "$LOG_FILE" + brew install yugabytedb 2>&1 | tee -a "$LOG_FILE" + if [ $? -ne 0 ]; then + log_message "ERROR" "Failed to install YugabyteDB via Homebrew." + return 1 + fi + echo "Starting YugabyteDB..." + yugabyted start 2>&1 | tee -a "$LOG_FILE" + ;; + + linux-debian|linux-redhat|linux-arch|linux-unknown) + echo "Installing YugabyteDB on Linux..." + # Download and install YugabyteDB + YUGABYTE_VERSION="2.20.1.0" + YUGABYTE_TAR="yugabyte-${YUGABYTE_VERSION}-linux-x86_64.tar.gz" + YUGABYTE_URL="https://downloads.yugabyte.com/releases/${YUGABYTE_VERSION}/${YUGABYTE_TAR}" + + echo "Downloading YugabyteDB ${YUGABYTE_VERSION}..." + log_message "INFO" "Downloading from $YUGABYTE_URL" + + if command -v wget &> /dev/null; then + wget -q "$YUGABYTE_URL" -O "/tmp/${YUGABYTE_TAR}" 2>&1 | tee -a "$LOG_FILE" + elif command -v curl &> /dev/null; then + curl -sL "$YUGABYTE_URL" -o "/tmp/${YUGABYTE_TAR}" 2>&1 | tee -a "$LOG_FILE" + else + log_message "ERROR" "Neither wget nor curl found. Cannot download YugabyteDB." + return 1 + fi + + echo "Extracting YugabyteDB..." + tar -xzf "/tmp/${YUGABYTE_TAR}" -C /opt 2>&1 | tee -a "$LOG_FILE" || \ + sudo tar -xzf "/tmp/${YUGABYTE_TAR}" -C /opt 2>&1 | tee -a "$LOG_FILE" + + YUGABYTE_HOME="/opt/yugabyte-${YUGABYTE_VERSION}" + export PATH="$YUGABYTE_HOME/bin:$PATH" + + echo "Starting YugabyteDB..." + "$YUGABYTE_HOME/bin/yugabyted" start 2>&1 | tee -a "$LOG_FILE" + ;; + + windows) + # ============================================================================ + # MANUAL INTERVENTION REQUIRED: Windows Native YugabyteDB + # ============================================================================ + # NOTE: YugabyteDB does not have a native Windows installer. + # Windows users must use Docker or WSL2 to run YugabyteDB. + # ============================================================================ + log_message "ERROR" "Native YugabyteDB installation is not supported on Windows." + echo "" + echo "==========================================" + echo "MANUAL STEP REQUIRED: Windows YugabyteDB" + echo "==========================================" + echo "" + echo "YugabyteDB does not have a native Windows installer." + echo "Please use one of these alternatives:" + echo "" + echo " 1. Docker Desktop for Windows:" + echo " docker run -d --name yugabyte -p7000:7000 -p9000:9000 -p5433:5433 -p9042:9042 yugabytedb/yugabyte:latest bin/yugabyted start --daemon=false" + echo "" + echo " 2. WSL2 (Windows Subsystem for Linux):" + echo " Run this script inside WSL2 with --yugabyte=native" + echo "" + log_message "INFO" "Windows users should use Docker or WSL2 for YugabyteDB." + return 1 + ;; + + *) + log_message "ERROR" "Unsupported OS for native YugabyteDB installation: $OS_TYPE" + return 1 + ;; + esac + + # Wait for YugabyteDB to be ready + echo "Waiting for YugabyteDB to initialize (30 seconds)..." + sleep 30 + + return 0 +} + +# Function to run a command with logging +run_command() { + local description="$1" + local command="$2" + local working_dir="${3:-$BASE_DIR}" + + echo "[$(date '+%Y-%m-%d %H:%M:%S')] RUNNING: $description" >> "$LOG_FILE" + echo " Command: $command" >> "$LOG_FILE" + echo " Directory: $working_dir" >> "$LOG_FILE" + + # Run command and capture output and exit code + cd "$working_dir" + output=$(eval "$command" 2>&1) + exit_code=$? + + if [ $exit_code -eq 0 ]; then + echo " Status: SUCCESS" >> "$LOG_FILE" + echo " Output: $output" >> "$LOG_FILE" + else + local error_desc=$(get_exit_code_description $exit_code) + echo " Status: FAILURE" >> "$LOG_FILE" + echo " Exit Code: $exit_code ($error_desc)" >> "$LOG_FILE" + echo " Error Output: $output" >> "$LOG_FILE" + fi + echo "" >> "$LOG_FILE" + + cd "$BASE_DIR" + return $exit_code +} + +# Function to run a background service with logging +run_service_background() { + local description="$1" + local command="$2" + local working_dir="${3:-$BASE_DIR}" + local log_prefix="$4" + + echo "[$(date '+%Y-%m-%d %H:%M:%S')] STARTING SERVICE: $description" >> "$LOG_FILE" + echo " Command: $command" >> "$LOG_FILE" + echo " Directory: $working_dir" >> "$LOG_FILE" + + cd "$working_dir" + nohup $command > "${BASE_DIR}/${log_prefix}.out" 2>&1 & + local pid=$! + + # Give the service a moment to start or fail immediately + sleep 3 + + if ps -p $pid > /dev/null 2>&1; then + echo " Status: SUCCESS (PID: $pid)" >> "$LOG_FILE" + echo " Service output logged to: ${log_prefix}.out" >> "$LOG_FILE" + echo " Started $description (PID: $pid)" + else + wait $pid 2>/dev/null + exit_code=$? + local error_desc=$(get_exit_code_description $exit_code) + echo " Status: FAILURE" >> "$LOG_FILE" + echo " Exit Code: $exit_code ($error_desc)" >> "$LOG_FILE" + echo " Error Output: $(tail -20 ${BASE_DIR}/${log_prefix}.out 2>/dev/null)" >> "$LOG_FILE" + echo " FAILED to start $description" + fi + echo "" >> "$LOG_FILE" + + cd "$BASE_DIR" +} + +# ============================================================================ +# MAIN SCRIPT +# ============================================================================ + +echo "==========================================" +echo "Yugastore Bootstrap Script" +echo "==========================================" +echo "Log file: $LOG_FILE" +echo "Operating System: $OS_TYPE" +echo "Interactive Mode: $INTERACTIVE" +echo "YugabyteDB Mode: $YUGABYTE_MODE" +echo "" + +# ============================================================================ +# PREREQUISITE CHECKS +# ============================================================================ +echo "Checking prerequisites..." +log_message "INFO" "=== PREREQUISITE CHECKS ===" + +# Check for package manager based on OS +case "$OS_TYPE" in + macos) + if ! command -v brew &> /dev/null; then + log_message "ERROR" "Homebrew is not installed. Please install it first:" + log_message "ERROR" " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" + echo "" + echo "ERROR: Homebrew is not installed." + echo "Please install Homebrew first by running:" + echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' + echo "" + echo "Then re-run this script." + exit 1 + fi + log_message "INFO" "Prerequisite check: brew found at $(which brew)" + ;; + linux-debian) + log_message "INFO" "Using apt-get package manager" + ;; + linux-redhat) + log_message "INFO" "Using dnf/yum package manager" + ;; + windows) + if ! command -v choco &> /dev/null && ! command -v winget &> /dev/null; then + log_message "WARNING" "Neither Chocolatey nor winget found. Package installation may fail." + fi + ;; +esac + +# Check for Java 17 +if ! command -v java &> /dev/null; then + log_message "WARNING" "Java not found. Attempting to install OpenJDK 17..." + install_java +fi + +# Verify Java version +if command -v java &> /dev/null; then + java_version=$(java -version 2>&1 | head -n 1) + log_message "INFO" "Java version: $java_version" +else + log_message "ERROR" "Java installation failed." + exit 1 +fi + +# Check for Maven +if ! command -v mvn &> /dev/null; then + log_message "WARNING" "Maven not found. Attempting to install..." + install_maven +fi + +if ! command -v mvn &> /dev/null; then + log_message "ERROR" "Maven installation failed." + exit 1 +fi + +# Check for Python 3 (needed for data loading) +if ! command -v python3 &> /dev/null; then + log_message "WARNING" "Python 3 not found. Attempting to install..." + install_python +fi + +# Check for cqlsh (Cassandra Query Language Shell) +if ! command -v cqlsh &> /dev/null; then + log_message "WARNING" "cqlsh not found. Attempting to install via pip..." + echo "Installing cqlsh via pip..." + pip3 install cqlsh 2>&1 | tee -a "$LOG_FILE" +fi + +# Check for psql (PostgreSQL client) +if ! command -v psql &> /dev/null; then + log_message "WARNING" "psql not found. Attempting to install PostgreSQL client..." + install_psql +fi + +# Add libpq to PATH on macOS if needed +if [ "$OS_TYPE" = "macos" ] && [ -d "/opt/homebrew/opt/libpq/bin" ]; then + export PATH="/opt/homebrew/opt/libpq/bin:$PATH" +fi + +# ============================================================================ +# YUGABYTEDB SETUP +# ============================================================================ +log_message "INFO" "=== YUGABYTEDB SETUP ===" +echo "" +echo "==========================================" +echo "Setting up YugabyteDB ($YUGABYTE_MODE mode)..." +echo "==========================================" + +if [ "$YUGABYTE_MODE" = "docker" ]; then + install_yugabyte_docker + yugabyte_result=$? +elif [ "$YUGABYTE_MODE" = "native" ]; then + install_yugabyte_native + yugabyte_result=$? +fi + +if [ $yugabyte_result -ne 0 ]; then + if [ "$INTERACTIVE" = true ]; then + echo "" + echo "YugabyteDB setup failed or requires manual intervention." + read -p "Do you want to continue anyway (assuming YugabyteDB is already running)? (y/n): " continue_anyway + if [ "$continue_anyway" != "y" ] && [ "$continue_anyway" != "Y" ]; then + log_message "ERROR" "User chose not to continue after YugabyteDB setup failure." + exit 1 + fi + else + log_message "ERROR" "YugabyteDB setup failed in non-interactive mode." + echo "ERROR: YugabyteDB setup failed. Please ensure YugabyteDB is running and re-run this script." + exit 1 + fi +fi + +# Verify YugabyteDB connectivity +echo "Verifying YugabyteDB connectivity..." +if command -v cqlsh &> /dev/null; then + if cqlsh -e "DESCRIBE KEYSPACES;" 2>/dev/null; then + log_message "INFO" "YugabyteDB YCQL connection verified" + echo " YCQL (Cassandra) connection: OK" + else + log_message "WARNING" "Could not connect to YugabyteDB YCQL on localhost:9042" + echo " WARNING: Could not connect to YCQL on localhost:9042" + fi +fi + +if command -v psql &> /dev/null; then + if psql -h localhost -p 5433 -U yugabyte -d yugabyte -c "SELECT 1;" &>/dev/null; then + log_message "INFO" "YugabyteDB YSQL connection verified" + echo " YSQL (PostgreSQL) connection: OK" + else + log_message "WARNING" "Could not connect to YugabyteDB YSQL on localhost:5433" + echo " WARNING: Could not connect to YSQL on localhost:5433" + fi +fi + +# ============================================================================ +# BUILD THE APPLICATION +# ============================================================================ +echo "" +echo "==========================================" +echo "Building the application..." +echo "==========================================" +log_message "INFO" "=== BUILD PHASE ===" + +# Skip Docker build if not using Docker mode (exec plugin runs docker build) +if [ "$YUGABYTE_MODE" = "docker" ]; then + MVN_BUILD_CMD="mvn -DskipTests package" +else + # Skip the exec plugin which runs docker build + MVN_BUILD_CMD="mvn -DskipTests -Dexec.skip=true package" + log_message "INFO" "Skipping Docker image builds (native mode)" +fi + +run_command "Build application with Maven" "$MVN_BUILD_CMD" +if [ $? -ne 0 ]; then + log_message "ERROR" "Build failed. Check $LOG_FILE for details." + echo "ERROR: Build failed. Check $LOG_FILE for details." + exit 1 +fi +echo "Build completed successfully." + +# ============================================================================ +# STEP 1: INSTALL AND INITIALIZE YUGABYTEDB +# ============================================================================ +echo "" +echo "==========================================" +echo "Step 1: Initializing YugabyteDB schemas..." +echo "==========================================" +log_message "INFO" "=== STEP 1: DATABASE INITIALIZATION ===" + +# Create CQL schema +echo "Creating CQL schema..." +run_command "Create CQL schema (cqlsh -f schema.cql)" "cqlsh -f schema.cql" "$BASE_DIR/resources" +if [ $? -ne 0 ]; then + log_message "WARNING" "CQL schema creation failed. Check $LOG_FILE for details." + echo "WARNING: CQL schema creation failed." +fi + +# Load sample data +echo "Loading sample data..." +run_command "Load sample data (./dataload.sh)" "./dataload.sh" "$BASE_DIR/resources" +if [ $? -ne 0 ]; then + log_message "WARNING" "Data load failed. Check $LOG_FILE for details." + echo "WARNING: Data load failed." +fi + +# Create YSQL tables +echo "Creating YSQL tables..." +run_command "Create YSQL schema (psql -f schema.sql)" "psql -h localhost -p 5433 -U yugabyte -d yugabyte -f schema.sql" "$BASE_DIR/resources" +if [ $? -ne 0 ]; then + log_message "WARNING" "YSQL schema creation failed. Check $LOG_FILE for details." + echo "WARNING: YSQL schema creation failed." +fi + +# ============================================================================ +# STEP 2: START EUREKA SERVICE DISCOVERY +# ============================================================================ +echo "" +echo "==========================================" +echo "Step 2: Starting Eureka service discovery..." +echo "==========================================" +log_message "INFO" "=== STEP 2: EUREKA SERVICE DISCOVERY ===" + +run_service_background "Eureka Service Discovery" "mvn spring-boot:run" "$BASE_DIR/eureka-server-local" "eureka-server" +echo "Waiting for Eureka to initialize (30 seconds)..." +sleep 30 + +# Verify Eureka is running +if curl -s http://localhost:8761 > /dev/null 2>&1; then + log_message "INFO" "Eureka Service Discovery is responding on http://localhost:8761" + echo "Eureka is running at http://localhost:8761" +else + log_message "WARNING" "Eureka may not be fully started yet. Check eureka-server.out for details." + echo "WARNING: Eureka may not be fully started. Check eureka-server.out" +fi + +# ============================================================================ +# STEP 2 (continued): START API GATEWAY MICROSERVICE +# ============================================================================ +echo "" +echo "==========================================" +echo "Starting API Gateway microservice..." +echo "==========================================" +log_message "INFO" "=== API GATEWAY MICROSERVICE ===" + +run_service_background "API Gateway Microservice" "mvn spring-boot:run" "$BASE_DIR/api-gateway-microservice" "api-gateway" +sleep 10 + +# ============================================================================ +# STEP 3: START PRODUCTS MICROSERVICE +# ============================================================================ +echo "" +echo "==========================================" +echo "Step 3: Starting Products microservice..." +echo "==========================================" +log_message "INFO" "=== STEP 3: PRODUCTS MICROSERVICE ===" + +run_service_background "Products Microservice" "mvn spring-boot:run" "$BASE_DIR/products-microservice" "products" +sleep 10 + +# ============================================================================ +# STEP 4: START CHECKOUT MICROSERVICE +# ============================================================================ +echo "" +echo "==========================================" +echo "Step 4: Starting Checkout microservice..." +echo "==========================================" +log_message "INFO" "=== STEP 4: CHECKOUT MICROSERVICE ===" + +run_service_background "Checkout Microservice" "mvn spring-boot:run" "$BASE_DIR/checkout-microservice" "checkout" +sleep 10 + +# ============================================================================ +# STEP 5: START CART MICROSERVICE +# ============================================================================ +echo "" +echo "==========================================" +echo "Step 5: Starting Cart microservice..." +echo "==========================================" +log_message "INFO" "=== STEP 5: CART MICROSERVICE ===" + +run_service_background "Cart Microservice" "mvn spring-boot:run" "$BASE_DIR/cart-microservice" "cart" +sleep 10 + +# ============================================================================ +# STEP 6: START THE UI +# ============================================================================ +echo "" +echo "==========================================" +echo "Step 6: Starting React UI..." +echo "==========================================" +log_message "INFO" "=== STEP 6: REACT UI ===" + +run_service_background "React UI" "mvn spring-boot:run" "$BASE_DIR/react-ui" "react-ui" +sleep 10 + +# ============================================================================ +# COMPLETION +# ============================================================================ +echo "" +echo "==========================================" | tee -a "$LOG_FILE" +echo "=== Bootstrap Complete ===" | tee -a "$LOG_FILE" +echo "==========================================" | tee -a "$LOG_FILE" +echo "Completed at: $(date)" >> "$LOG_FILE" +echo "" +echo "Services should be available at:" +echo " - Eureka Dashboard: http://localhost:8761/" +echo " - API Gateway: http://localhost:8081/" +echo " - Products: http://localhost:8082/" +echo " - Cart: http://localhost:8083/" +echo " - Login: http://localhost:8085/" +echo " - Checkout: http://localhost:8086/" +echo " - Marketplace App: http://localhost:8080/" +echo "" +echo "Log files:" +echo " - Main log: $LOG_FILE" +echo " - Eureka: eureka-server.out" +echo " - API Gateway: api-gateway.out" +echo " - Products: products.out" +echo " - Checkout: checkout.out" +echo " - Cart: cart.out" +echo " - React UI: react-ui.out" +echo "" +echo "To stop all services, run:" +echo " pkill -f 'spring-boot:run'" +echo "" +if [ "$YUGABYTE_MODE" = "docker" ]; then + echo "To stop YugabyteDB Docker container:" + echo " docker stop yugabyte" + echo "" +fi diff --git a/scripts/tests/README.md b/scripts/tests/README.md new file mode 100644 index 0000000..b64ad8a --- /dev/null +++ b/scripts/tests/README.md @@ -0,0 +1,151 @@ +# Bootstrap Script Tests + +This directory contains unit tests for the `bootstrap.sh` script using the BATS (Bash Automated Testing System) framework. + +## Prerequisites + +### Install BATS + +**macOS (Homebrew):** +```bash +brew install bats-core +``` + +**Ubuntu/Debian:** +```bash +sudo apt-get install bats +``` + +**Fedora/RHEL:** +```bash +sudo dnf install bats +``` + +**From source:** +```bash +git clone https://github.com/bats-core/bats-core.git +cd bats-core +./install.sh /usr/local +``` + +## Running the Tests + +From the repository root directory: + +```bash +bats scripts/tests/bootstrap_test.bats +``` + +Or from the scripts directory: + +```bash +cd scripts +bats tests/bootstrap_test.bats +``` + +### Verbose Output + +For more detailed output showing each test: + +```bash +bats --tap scripts/tests/bootstrap_test.bats +``` + +### Run Specific Tests + +To run tests matching a pattern: + +```bash +bats --filter "help" scripts/tests/bootstrap_test.bats +``` + +## Test Coverage + +The test suite covers the following areas: + +| Category | Description | Test Count | +|----------|-------------|------------| +| Help and Usage | `--help` and `-h` flag functionality | 8 | +| Argument Parsing | Command-line option handling and error cases | 2 | +| Script Structure | Presence of required functions | 8 | +| Default Values | Correct initialization of variables | 3 | +| Prerequisite Checks | Detection of required tools (java, mvn, python3, etc.) | 5 | +| Exit Code Mapping | Proper error code descriptions | 4 | +| YugabyteDB Mode | Docker and native installation functions | 4 | +| Microservice Startup | All 6 microservices are started | 6 | +| Build | Maven build configuration | 2 | +| Schema and Data | Database initialization steps | 3 | +| Logging | Log file and log levels | 4 | +| Output Information | Service URLs and stop instructions | 2 | +| Package Managers | Support for apt, yum, dnf, brew | 3 | +| Error Handling | Exit codes and missing prerequisites | 3 | +| Port Configuration | Correct ports for all services | 8 | + +**Total: 65 tests** + +## Test File Structure + +``` +scripts/ +├── bootstrap.sh # Main bootstrap script +└── tests/ + ├── README.md # This file + └── bootstrap_test.bats # BATS test suite +``` + +## Writing Additional Tests + +BATS tests follow this structure: + +```bash +@test "description of test" { + run command_to_test + [ "$status" -eq 0 ] # Check exit status + [[ "$output" == *"expected"* ]] # Check output contains string +} +``` + +### Setup and Teardown + +The test file includes `setup()` and `teardown()` functions that run before and after each test: + +- `setup()`: Creates temp directories and sets up paths +- `teardown()`: Cleans up temp files + +## Continuous Integration + +To run tests in CI/CD pipelines, use: + +```bash +bats --formatter tap scripts/tests/bootstrap_test.bats +``` + +This outputs TAP (Test Anything Protocol) format suitable for CI systems. + +## Troubleshooting + +### Tests not finding bootstrap.sh + +Ensure you're running tests from the repository root: + +```bash +cd /path/to/bookstore-r-us +bats scripts/tests/bootstrap_test.bats +``` + +### Permission denied + +Make sure the test file is executable: + +```bash +chmod +x scripts/tests/bootstrap_test.bats +``` + +### BATS not found + +Verify BATS is installed and in your PATH: + +```bash +which bats +bats --version +``` diff --git a/scripts/tests/bootstrap_test.bats b/scripts/tests/bootstrap_test.bats new file mode 100755 index 0000000..e33b674 --- /dev/null +++ b/scripts/tests/bootstrap_test.bats @@ -0,0 +1,427 @@ +#!/usr/bin/env bats +# Unit tests for bootstrap.sh +# Run with: bats scripts/tests/bootstrap_test.bats + +# Setup - runs before each test +setup() { + # Get the directory where tests are located + TESTS_DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" && pwd )" + SCRIPTS_DIR="$(dirname "$TESTS_DIR")" + BOOTSTRAP_SCRIPT="$SCRIPTS_DIR/bootstrap.sh" + + # Create a temp directory for test artifacts + TEST_TEMP_DIR="$(mktemp -d)" + + # Export for use in tests + export TESTS_DIR SCRIPTS_DIR BOOTSTRAP_SCRIPT TEST_TEMP_DIR +} + +# Teardown - runs after each test +teardown() { + # Clean up temp directory + if [ -d "$TEST_TEMP_DIR" ]; then + rm -rf "$TEST_TEMP_DIR" + fi +} + +# ============================================================================= +# HELP AND USAGE TESTS +# ============================================================================= + +@test "bootstrap.sh exists and is executable" { + [ -f "$BOOTSTRAP_SCRIPT" ] + [ -x "$BOOTSTRAP_SCRIPT" ] +} + +@test "--help flag displays usage information" { + run "$BOOTSTRAP_SCRIPT" --help + [ "$status" -eq 0 ] + [[ "$output" == *"Yugastore Bootstrap Script"* ]] + [[ "$output" == *"Usage:"* ]] + [[ "$output" == *"Options:"* ]] +} + +@test "-h flag displays usage information" { + run "$BOOTSTRAP_SCRIPT" -h + [ "$status" -eq 0 ] + [[ "$output" == *"Yugastore Bootstrap Script"* ]] + [[ "$output" == *"Usage:"* ]] +} + +@test "--help shows --non-interactive option" { + run "$BOOTSTRAP_SCRIPT" --help + [ "$status" -eq 0 ] + [[ "$output" == *"--non-interactive"* ]] +} + +@test "--help shows --yugabyte=docker option" { + run "$BOOTSTRAP_SCRIPT" --help + [ "$status" -eq 0 ] + [[ "$output" == *"--yugabyte=docker"* ]] +} + +@test "--help shows --yugabyte=native option" { + run "$BOOTSTRAP_SCRIPT" --help + [ "$status" -eq 0 ] + [[ "$output" == *"--yugabyte=native"* ]] +} + +@test "--help shows examples section" { + run "$BOOTSTRAP_SCRIPT" --help + [ "$status" -eq 0 ] + [[ "$output" == *"Examples:"* ]] +} + +@test "--help shows supported operating systems" { + run "$BOOTSTRAP_SCRIPT" --help + [ "$status" -eq 0 ] + [[ "$output" == *"Supported Operating Systems:"* ]] + [[ "$output" == *"macOS"* ]] + [[ "$output" == *"Linux"* ]] +} + +# ============================================================================= +# ARGUMENT PARSING TESTS +# ============================================================================= + +@test "unknown option shows error and usage" { + run "$BOOTSTRAP_SCRIPT" --unknown-option + [ "$status" -eq 1 ] + [[ "$output" == *"Unknown option: --unknown-option"* ]] + [[ "$output" == *"Usage:"* ]] +} + +@test "invalid yugabyte option shows error" { + run "$BOOTSTRAP_SCRIPT" --yugabyte=invalid + [ "$status" -eq 1 ] + [[ "$output" == *"Unknown option"* ]] +} + +# ============================================================================= +# SCRIPT STRUCTURE TESTS +# ============================================================================= + +@test "script contains show_help function" { + run grep -q "show_help()" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script contains detect_os function" { + run grep -q "detect_os()" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script contains log_message function" { + run grep -q "log_message()" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script contains run_command function" { + run grep -q "run_command()" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script contains get_exit_code_description function" { + run grep -q "get_exit_code_description()" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script handles Darwin (macOS) in detect_os" { + run grep -q "Darwin" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script handles Linux in detect_os" { + run grep -q "Linux" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script handles Windows/CYGWIN in detect_os" { + run grep -q "CYGWIN\|MINGW\|MSYS" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# DEFAULT VALUES TESTS +# ============================================================================= + +@test "script sets default INTERACTIVE to true" { + run grep -q 'INTERACTIVE=true' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script sets default YUGABYTE_MODE to docker" { + run grep -q 'YUGABYTE_MODE="docker"' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script defines LOG_FILE variable" { + run grep -q 'LOG_FILE=' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# PREREQUISITE CHECK TESTS +# ============================================================================= + +@test "script checks for java prerequisite" { + run grep -q "java" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script checks for mvn prerequisite" { + run grep -q "mvn" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script checks for python3 prerequisite" { + run grep -q "python3" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script checks for cqlsh prerequisite" { + run grep -q "cqlsh" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script checks for psql prerequisite" { + run grep -q "psql" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# EXIT CODE MAPPING TESTS +# ============================================================================= + +@test "script maps exit code 0 to Success" { + run grep -A1 'case.*code.*in' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] + run grep -q '"Success"' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script maps exit code 1 to General error" { + run grep -q "General error" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script maps exit code 127 to Command not found" { + run grep -q "Command not found" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script maps exit code 126 to permission problem" { + run grep -q "permission problem\|cannot execute" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# YUGABYTE MODE TESTS +# ============================================================================= + +@test "script has install_yugabyte_docker function" { + run grep -q "install_yugabyte_docker()" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script has install_yugabyte_native function" { + run grep -q "install_yugabyte_native()" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses Docker for yugabyte when mode is docker" { + run grep -q 'docker run.*yugabyte' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses homebrew for macOS native install" { + run grep -q "brew.*yugabytedb\|brew tap yugabyte" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# MICROSERVICE STARTUP TESTS +# ============================================================================= + +@test "script starts eureka service" { + run grep -qi "eureka" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script starts api-gateway microservice" { + run grep -qi "api-gateway" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script starts products microservice" { + run grep -qi "products" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script starts checkout microservice" { + run grep -qi "checkout" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script starts cart microservice" { + run grep -qi "cart" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script starts react-ui" { + run grep -qi "react-ui" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# BUILD TESTS +# ============================================================================= + +@test "script runs maven build with -DskipTests" { + run grep -q 'mvn.*-DskipTests.*package' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script skips docker build in native mode with -Dexec.skip" { + run grep -q '\-Dexec.skip=true' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# SCHEMA AND DATA TESTS +# ============================================================================= + +@test "script creates CQL schema" { + run grep -qi "schema.cql\|cqlsh.*-f" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script loads sample data" { + run grep -qi "dataload\|sample.*data" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script creates SQL tables" { + run grep -qi "schema.sql\|psql.*-f" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# LOGGING TESTS +# ============================================================================= + +@test "script logs to bootstrap.log" { + run grep -q 'bootstrap.log' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script has INFO log level" { + run grep -q '"INFO"' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script has ERROR log level" { + run grep -q '"ERROR"' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script has WARNING log level" { + run grep -q '"WARNING"' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# OUTPUT INFORMATION TESTS +# ============================================================================= + +@test "script displays service URLs on completion" { + run grep -q "localhost:8761\|localhost:8080\|localhost:8081" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script shows how to stop services" { + run grep -qi "pkill\|stop.*services" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# PACKAGE MANAGER TESTS +# ============================================================================= + +@test "script supports apt-get for Debian-based Linux" { + run grep -q "apt-get\|apt " "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script supports yum/dnf for RedHat-based Linux" { + run grep -q "yum\|dnf" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script supports brew for macOS" { + run grep -q "brew install\|brew " "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# ERROR HANDLING TESTS +# ============================================================================= + +@test "script checks command exit codes" { + run grep -q '\$?' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script exits on build failure" { + run grep -q 'exit 1' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script handles missing prerequisites" { + run grep -qi "missing\|not found\|installing" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# PORT CONFIGURATION TESTS +# ============================================================================= + +@test "script uses port 8761 for Eureka" { + run grep -q "8761" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses port 8080 for React UI" { + run grep -q "8080" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses port 8081 for API Gateway" { + run grep -q "8081" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses port 8082 for Products" { + run grep -q "8082" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses port 8083 for Cart" { + run grep -q "8083" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses port 8086 for Checkout" { + run grep -q "8086" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses port 9042 for YCQL/Cassandra" { + run grep -q "9042" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses port 5433 for YSQL/PostgreSQL" { + run grep -q "5433" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} From 98bf296a1f7fcd28160e12963292039399710af3 Mon Sep 17 00:00:00 2001 From: Steven French Date: Mon, 8 Dec 2025 13:08:20 -0500 Subject: [PATCH 04/13] Add clickable frontend URL and expand test coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add prominent FRONTEND URL section at end of bootstrap output - Implement OSC 8 escape sequence for clickable hyperlinks in supported terminals (iTerm2, VS Code, modern Linux terminals) - Add 4 new BATS tests for frontend URL display functionality: - Verifies FRONTEND URL section exists - Verifies printf is used for URL output - Verifies OSC 8 escape sequence for hyperlink - Verifies "click to open" hint text - Update test README with new test count (65 -> 69) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- scripts/bootstrap.sh | 9 ++++++++- scripts/tests/README.md | 3 ++- scripts/tests/bootstrap_test.bats | 25 +++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index cce1428..265b0d3 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -902,7 +902,14 @@ echo " - Products: http://localhost:8082/" echo " - Cart: http://localhost:8083/" echo " - Login: http://localhost:8085/" echo " - Checkout: http://localhost:8086/" -echo " - Marketplace App: http://localhost:8080/" +echo "" +echo "==========================================" +echo " FRONTEND URL (click to open):" +echo "" +# Use OSC 8 hyperlink escape sequence for clickable URL in supported terminals +printf " \033]8;;http://localhost:8080/\033\\http://localhost:8080/\033]8;;\033\\\n" +echo "" +echo "==========================================" echo "" echo "Log files:" echo " - Main log: $LOG_FILE" diff --git a/scripts/tests/README.md b/scripts/tests/README.md index b64ad8a..371dcfc 100644 --- a/scripts/tests/README.md +++ b/scripts/tests/README.md @@ -80,8 +80,9 @@ The test suite covers the following areas: | Package Managers | Support for apt, yum, dnf, brew | 3 | | Error Handling | Exit codes and missing prerequisites | 3 | | Port Configuration | Correct ports for all services | 8 | +| Frontend URL Display | Clickable URL with OSC 8 escape sequence | 4 | -**Total: 65 tests** +**Total: 69 tests** ## Test File Structure diff --git a/scripts/tests/bootstrap_test.bats b/scripts/tests/bootstrap_test.bats index e33b674..8e5df00 100755 --- a/scripts/tests/bootstrap_test.bats +++ b/scripts/tests/bootstrap_test.bats @@ -425,3 +425,28 @@ teardown() { run grep -q "5433" "$BOOTSTRAP_SCRIPT" [ "$status" -eq 0 ] } + +# ============================================================================= +# FRONTEND URL DISPLAY TESTS +# ============================================================================= + +@test "script displays prominent FRONTEND URL section" { + run grep -q "FRONTEND URL" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses printf for clickable URL" { + run grep -q 'printf.*http://localhost:8080' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script uses OSC 8 escape sequence for hyperlink" { + # OSC 8 is the escape sequence for clickable hyperlinks in terminals + run grep -q '\\033\]8;;' "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} + +@test "script displays click to open hint" { + run grep -qi "click to open" "$BOOTSTRAP_SCRIPT" + [ "$status" -eq 0 ] +} From 6ff6edfbd6fc985fda4e3a1a22cdead89c5c515d Mon Sep 17 00:00:00 2001 From: ironchef001 Date: Mon, 8 Dec 2025 13:39:35 -0500 Subject: [PATCH 05/13] Add Speckit configuration and Cursor AI rules - Add .specify/ directory with Speckit configuration - Add .cursor/ directory with AI coding assistant rules - Configure project context and development guidelines for AI assistants --- .cursor/commands/speckit.analyze.md | 184 ++++ .cursor/commands/speckit.checklist.md | 294 +++++++ .cursor/commands/speckit.clarify.md | 181 ++++ .cursor/commands/speckit.constitution.md | 82 ++ .cursor/commands/speckit.implement.md | 135 +++ .cursor/commands/speckit.plan.md | 89 ++ .cursor/commands/speckit.specify.md | 258 ++++++ .cursor/commands/speckit.tasks.md | 137 +++ .cursor/commands/speckit.taskstoissues.md | 30 + .specify/memory/constitution.md | 50 ++ .specify/scripts/bash/check-prerequisites.sh | 166 ++++ .specify/scripts/bash/common.sh | 156 ++++ .specify/scripts/bash/create-new-feature.sh | 297 +++++++ .specify/scripts/bash/setup-plan.sh | 61 ++ .specify/scripts/bash/update-agent-context.sh | 799 ++++++++++++++++++ .specify/templates/agent-file-template.md | 28 + .specify/templates/checklist-template.md | 40 + .specify/templates/plan-template.md | 104 +++ .specify/templates/spec-template.md | 115 +++ .specify/templates/tasks-template.md | 251 ++++++ 20 files changed, 3457 insertions(+) create mode 100644 .cursor/commands/speckit.analyze.md create mode 100644 .cursor/commands/speckit.checklist.md create mode 100644 .cursor/commands/speckit.clarify.md create mode 100644 .cursor/commands/speckit.constitution.md create mode 100644 .cursor/commands/speckit.implement.md create mode 100644 .cursor/commands/speckit.plan.md create mode 100644 .cursor/commands/speckit.specify.md create mode 100644 .cursor/commands/speckit.tasks.md create mode 100644 .cursor/commands/speckit.taskstoissues.md create mode 100644 .specify/memory/constitution.md create mode 100755 .specify/scripts/bash/check-prerequisites.sh create mode 100755 .specify/scripts/bash/common.sh create mode 100755 .specify/scripts/bash/create-new-feature.sh create mode 100755 .specify/scripts/bash/setup-plan.sh create mode 100755 .specify/scripts/bash/update-agent-context.sh create mode 100644 .specify/templates/agent-file-template.md create mode 100644 .specify/templates/checklist-template.md create mode 100644 .specify/templates/plan-template.md create mode 100644 .specify/templates/spec-template.md create mode 100644 .specify/templates/tasks-template.md diff --git a/.cursor/commands/speckit.analyze.md b/.cursor/commands/speckit.analyze.md new file mode 100644 index 0000000..98b04b0 --- /dev/null +++ b/.cursor/commands/speckit.analyze.md @@ -0,0 +1,184 @@ +--- +description: Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation. +--- + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Goal + +Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/speckit.tasks` has successfully produced a complete `tasks.md`. + +## Operating Constraints + +**STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually). + +**Constitution Authority**: The project constitution (`.specify/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/speckit.analyze`. + +## Execution Steps + +### 1. Initialize Analysis Context + +Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths: + +- SPEC = FEATURE_DIR/spec.md +- PLAN = FEATURE_DIR/plan.md +- TASKS = FEATURE_DIR/tasks.md + +Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command). +For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). + +### 2. Load Artifacts (Progressive Disclosure) + +Load only the minimal necessary context from each artifact: + +**From spec.md:** + +- Overview/Context +- Functional Requirements +- Non-Functional Requirements +- User Stories +- Edge Cases (if present) + +**From plan.md:** + +- Architecture/stack choices +- Data Model references +- Phases +- Technical constraints + +**From tasks.md:** + +- Task IDs +- Descriptions +- Phase grouping +- Parallel markers [P] +- Referenced file paths + +**From constitution:** + +- Load `.specify/memory/constitution.md` for principle validation + +### 3. Build Semantic Models + +Create internal representations (do not include raw artifacts in output): + +- **Requirements inventory**: Each functional + non-functional requirement with a stable key (derive slug based on imperative phrase; e.g., "User can upload file" → `user-can-upload-file`) +- **User story/action inventory**: Discrete user actions with acceptance criteria +- **Task coverage mapping**: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases) +- **Constitution rule set**: Extract principle names and MUST/SHOULD normative statements + +### 4. Detection Passes (Token-Efficient Analysis) + +Focus on high-signal findings. Limit to 50 findings total; aggregate remainder in overflow summary. + +#### A. Duplication Detection + +- Identify near-duplicate requirements +- Mark lower-quality phrasing for consolidation + +#### B. Ambiguity Detection + +- Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria +- Flag unresolved placeholders (TODO, TKTK, ???, ``, etc.) + +#### C. Underspecification + +- Requirements with verbs but missing object or measurable outcome +- User stories missing acceptance criteria alignment +- Tasks referencing files or components not defined in spec/plan + +#### D. Constitution Alignment + +- Any requirement or plan element conflicting with a MUST principle +- Missing mandated sections or quality gates from constitution + +#### E. Coverage Gaps + +- Requirements with zero associated tasks +- Tasks with no mapped requirement/story +- Non-functional requirements not reflected in tasks (e.g., performance, security) + +#### F. Inconsistency + +- Terminology drift (same concept named differently across files) +- Data entities referenced in plan but absent in spec (or vice versa) +- Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note) +- Conflicting requirements (e.g., one requires Next.js while other specifies Vue) + +### 5. Severity Assignment + +Use this heuristic to prioritize findings: + +- **CRITICAL**: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality +- **HIGH**: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion +- **MEDIUM**: Terminology drift, missing non-functional task coverage, underspecified edge case +- **LOW**: Style/wording improvements, minor redundancy not affecting execution order + +### 6. Produce Compact Analysis Report + +Output a Markdown report (no file writes) with the following structure: + +## Specification Analysis Report + +| ID | Category | Severity | Location(s) | Summary | Recommendation | +|----|----------|----------|-------------|---------|----------------| +| A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version | + +(Add one row per finding; generate stable IDs prefixed by category initial.) + +**Coverage Summary Table:** + +| Requirement Key | Has Task? | Task IDs | Notes | +|-----------------|-----------|----------|-------| + +**Constitution Alignment Issues:** (if any) + +**Unmapped Tasks:** (if any) + +**Metrics:** + +- Total Requirements +- Total Tasks +- Coverage % (requirements with >=1 task) +- Ambiguity Count +- Duplication Count +- Critical Issues Count + +### 7. Provide Next Actions + +At end of report, output a concise Next Actions block: + +- If CRITICAL issues exist: Recommend resolving before `/speckit.implement` +- If only LOW/MEDIUM: User may proceed, but provide improvement suggestions +- Provide explicit command suggestions: e.g., "Run /speckit.specify with refinement", "Run /speckit.plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'" + +### 8. Offer Remediation + +Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.) + +## Operating Principles + +### Context Efficiency + +- **Minimal high-signal tokens**: Focus on actionable findings, not exhaustive documentation +- **Progressive disclosure**: Load artifacts incrementally; don't dump all content into analysis +- **Token-efficient output**: Limit findings table to 50 rows; summarize overflow +- **Deterministic results**: Rerunning without changes should produce consistent IDs and counts + +### Analysis Guidelines + +- **NEVER modify files** (this is read-only analysis) +- **NEVER hallucinate missing sections** (if absent, report them accurately) +- **Prioritize constitution violations** (these are always CRITICAL) +- **Use examples over exhaustive rules** (cite specific instances, not generic patterns) +- **Report zero issues gracefully** (emit success report with coverage statistics) + +## Context + +$ARGUMENTS diff --git a/.cursor/commands/speckit.checklist.md b/.cursor/commands/speckit.checklist.md new file mode 100644 index 0000000..970e6c9 --- /dev/null +++ b/.cursor/commands/speckit.checklist.md @@ -0,0 +1,294 @@ +--- +description: Generate a custom checklist for the current feature based on user requirements. +--- + +## Checklist Purpose: "Unit Tests for English" + +**CRITICAL CONCEPT**: Checklists are **UNIT TESTS FOR REQUIREMENTS WRITING** - they validate the quality, clarity, and completeness of requirements in a given domain. + +**NOT for verification/testing**: + +- ❌ NOT "Verify the button clicks correctly" +- ❌ NOT "Test error handling works" +- ❌ NOT "Confirm the API returns 200" +- ❌ NOT checking if code/implementation matches the spec + +**FOR requirements quality validation**: + +- ✅ "Are visual hierarchy requirements defined for all card types?" (completeness) +- ✅ "Is 'prominent display' quantified with specific sizing/positioning?" (clarity) +- ✅ "Are hover state requirements consistent across all interactive elements?" (consistency) +- ✅ "Are accessibility requirements defined for keyboard navigation?" (coverage) +- ✅ "Does the spec define what happens when logo image fails to load?" (edge cases) + +**Metaphor**: If your spec is code written in English, the checklist is its unit test suite. You're testing whether the requirements are well-written, complete, unambiguous, and ready for implementation - NOT whether the implementation works. + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Execution Steps + +1. **Setup**: Run `.specify/scripts/bash/check-prerequisites.sh --json` from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS list. + - All file paths must be absolute. + - For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). + +2. **Clarify intent (dynamic)**: Derive up to THREE initial contextual clarifying questions (no pre-baked catalog). They MUST: + - Be generated from the user's phrasing + extracted signals from spec/plan/tasks + - Only ask about information that materially changes checklist content + - Be skipped individually if already unambiguous in `$ARGUMENTS` + - Prefer precision over breadth + + Generation algorithm: + 1. Extract signals: feature domain keywords (e.g., auth, latency, UX, API), risk indicators ("critical", "must", "compliance"), stakeholder hints ("QA", "review", "security team"), and explicit deliverables ("a11y", "rollback", "contracts"). + 2. Cluster signals into candidate focus areas (max 4) ranked by relevance. + 3. Identify probable audience & timing (author, reviewer, QA, release) if not explicit. + 4. Detect missing dimensions: scope breadth, depth/rigor, risk emphasis, exclusion boundaries, measurable acceptance criteria. + 5. Formulate questions chosen from these archetypes: + - Scope refinement (e.g., "Should this include integration touchpoints with X and Y or stay limited to local module correctness?") + - Risk prioritization (e.g., "Which of these potential risk areas should receive mandatory gating checks?") + - Depth calibration (e.g., "Is this a lightweight pre-commit sanity list or a formal release gate?") + - Audience framing (e.g., "Will this be used by the author only or peers during PR review?") + - Boundary exclusion (e.g., "Should we explicitly exclude performance tuning items this round?") + - Scenario class gap (e.g., "No recovery flows detected—are rollback / partial failure paths in scope?") + + Question formatting rules: + - If presenting options, generate a compact table with columns: Option | Candidate | Why It Matters + - Limit to A–E options maximum; omit table if a free-form answer is clearer + - Never ask the user to restate what they already said + - Avoid speculative categories (no hallucination). If uncertain, ask explicitly: "Confirm whether X belongs in scope." + + Defaults when interaction impossible: + - Depth: Standard + - Audience: Reviewer (PR) if code-related; Author otherwise + - Focus: Top 2 relevance clusters + + Output the questions (label Q1/Q2/Q3). After answers: if ≥2 scenario classes (Alternate / Exception / Recovery / Non-Functional domain) remain unclear, you MAY ask up to TWO more targeted follow‑ups (Q4/Q5) with a one-line justification each (e.g., "Unresolved recovery path risk"). Do not exceed five total questions. Skip escalation if user explicitly declines more. + +3. **Understand user request**: Combine `$ARGUMENTS` + clarifying answers: + - Derive checklist theme (e.g., security, review, deploy, ux) + - Consolidate explicit must-have items mentioned by user + - Map focus selections to category scaffolding + - Infer any missing context from spec/plan/tasks (do NOT hallucinate) + +4. **Load feature context**: Read from FEATURE_DIR: + - spec.md: Feature requirements and scope + - plan.md (if exists): Technical details, dependencies + - tasks.md (if exists): Implementation tasks + + **Context Loading Strategy**: + - Load only necessary portions relevant to active focus areas (avoid full-file dumping) + - Prefer summarizing long sections into concise scenario/requirement bullets + - Use progressive disclosure: add follow-on retrieval only if gaps detected + - If source docs are large, generate interim summary items instead of embedding raw text + +5. **Generate checklist** - Create "Unit Tests for Requirements": + - Create `FEATURE_DIR/checklists/` directory if it doesn't exist + - Generate unique checklist filename: + - Use short, descriptive name based on domain (e.g., `ux.md`, `api.md`, `security.md`) + - Format: `[domain].md` + - If file exists, append to existing file + - Number items sequentially starting from CHK001 + - Each `/speckit.checklist` run creates a NEW file (never overwrites existing checklists) + + **CORE PRINCIPLE - Test the Requirements, Not the Implementation**: + Every checklist item MUST evaluate the REQUIREMENTS THEMSELVES for: + - **Completeness**: Are all necessary requirements present? + - **Clarity**: Are requirements unambiguous and specific? + - **Consistency**: Do requirements align with each other? + - **Measurability**: Can requirements be objectively verified? + - **Coverage**: Are all scenarios/edge cases addressed? + + **Category Structure** - Group items by requirement quality dimensions: + - **Requirement Completeness** (Are all necessary requirements documented?) + - **Requirement Clarity** (Are requirements specific and unambiguous?) + - **Requirement Consistency** (Do requirements align without conflicts?) + - **Acceptance Criteria Quality** (Are success criteria measurable?) + - **Scenario Coverage** (Are all flows/cases addressed?) + - **Edge Case Coverage** (Are boundary conditions defined?) + - **Non-Functional Requirements** (Performance, Security, Accessibility, etc. - are they specified?) + - **Dependencies & Assumptions** (Are they documented and validated?) + - **Ambiguities & Conflicts** (What needs clarification?) + + **HOW TO WRITE CHECKLIST ITEMS - "Unit Tests for English"**: + + ❌ **WRONG** (Testing implementation): + - "Verify landing page displays 3 episode cards" + - "Test hover states work on desktop" + - "Confirm logo click navigates home" + + ✅ **CORRECT** (Testing requirements quality): + - "Are the exact number and layout of featured episodes specified?" [Completeness] + - "Is 'prominent display' quantified with specific sizing/positioning?" [Clarity] + - "Are hover state requirements consistent across all interactive elements?" [Consistency] + - "Are keyboard navigation requirements defined for all interactive UI?" [Coverage] + - "Is the fallback behavior specified when logo image fails to load?" [Edge Cases] + - "Are loading states defined for asynchronous episode data?" [Completeness] + - "Does the spec define visual hierarchy for competing UI elements?" [Clarity] + + **ITEM STRUCTURE**: + Each item should follow this pattern: + - Question format asking about requirement quality + - Focus on what's WRITTEN (or not written) in the spec/plan + - Include quality dimension in brackets [Completeness/Clarity/Consistency/etc.] + - Reference spec section `[Spec §X.Y]` when checking existing requirements + - Use `[Gap]` marker when checking for missing requirements + + **EXAMPLES BY QUALITY DIMENSION**: + + Completeness: + - "Are error handling requirements defined for all API failure modes? [Gap]" + - "Are accessibility requirements specified for all interactive elements? [Completeness]" + - "Are mobile breakpoint requirements defined for responsive layouts? [Gap]" + + Clarity: + - "Is 'fast loading' quantified with specific timing thresholds? [Clarity, Spec §NFR-2]" + - "Are 'related episodes' selection criteria explicitly defined? [Clarity, Spec §FR-5]" + - "Is 'prominent' defined with measurable visual properties? [Ambiguity, Spec §FR-4]" + + Consistency: + - "Do navigation requirements align across all pages? [Consistency, Spec §FR-10]" + - "Are card component requirements consistent between landing and detail pages? [Consistency]" + + Coverage: + - "Are requirements defined for zero-state scenarios (no episodes)? [Coverage, Edge Case]" + - "Are concurrent user interaction scenarios addressed? [Coverage, Gap]" + - "Are requirements specified for partial data loading failures? [Coverage, Exception Flow]" + + Measurability: + - "Are visual hierarchy requirements measurable/testable? [Acceptance Criteria, Spec §FR-1]" + - "Can 'balanced visual weight' be objectively verified? [Measurability, Spec §FR-2]" + + **Scenario Classification & Coverage** (Requirements Quality Focus): + - Check if requirements exist for: Primary, Alternate, Exception/Error, Recovery, Non-Functional scenarios + - For each scenario class, ask: "Are [scenario type] requirements complete, clear, and consistent?" + - If scenario class missing: "Are [scenario type] requirements intentionally excluded or missing? [Gap]" + - Include resilience/rollback when state mutation occurs: "Are rollback requirements defined for migration failures? [Gap]" + + **Traceability Requirements**: + - MINIMUM: ≥80% of items MUST include at least one traceability reference + - Each item should reference: spec section `[Spec §X.Y]`, or use markers: `[Gap]`, `[Ambiguity]`, `[Conflict]`, `[Assumption]` + - If no ID system exists: "Is a requirement & acceptance criteria ID scheme established? [Traceability]" + + **Surface & Resolve Issues** (Requirements Quality Problems): + Ask questions about the requirements themselves: + - Ambiguities: "Is the term 'fast' quantified with specific metrics? [Ambiguity, Spec §NFR-1]" + - Conflicts: "Do navigation requirements conflict between §FR-10 and §FR-10a? [Conflict]" + - Assumptions: "Is the assumption of 'always available podcast API' validated? [Assumption]" + - Dependencies: "Are external podcast API requirements documented? [Dependency, Gap]" + - Missing definitions: "Is 'visual hierarchy' defined with measurable criteria? [Gap]" + + **Content Consolidation**: + - Soft cap: If raw candidate items > 40, prioritize by risk/impact + - Merge near-duplicates checking the same requirement aspect + - If >5 low-impact edge cases, create one item: "Are edge cases X, Y, Z addressed in requirements? [Coverage]" + + **🚫 ABSOLUTELY PROHIBITED** - These make it an implementation test, not a requirements test: + - ❌ Any item starting with "Verify", "Test", "Confirm", "Check" + implementation behavior + - ❌ References to code execution, user actions, system behavior + - ❌ "Displays correctly", "works properly", "functions as expected" + - ❌ "Click", "navigate", "render", "load", "execute" + - ❌ Test cases, test plans, QA procedures + - ❌ Implementation details (frameworks, APIs, algorithms) + + **✅ REQUIRED PATTERNS** - These test requirements quality: + - ✅ "Are [requirement type] defined/specified/documented for [scenario]?" + - ✅ "Is [vague term] quantified/clarified with specific criteria?" + - ✅ "Are requirements consistent between [section A] and [section B]?" + - ✅ "Can [requirement] be objectively measured/verified?" + - ✅ "Are [edge cases/scenarios] addressed in requirements?" + - ✅ "Does the spec define [missing aspect]?" + +6. **Structure Reference**: Generate the checklist following the canonical template in `.specify/templates/checklist-template.md` for title, meta section, category headings, and ID formatting. If template is unavailable, use: H1 title, purpose/created meta lines, `##` category sections containing `- [ ] CHK### ` lines with globally incrementing IDs starting at CHK001. + +7. **Report**: Output full path to created checklist, item count, and remind user that each run creates a new file. Summarize: + - Focus areas selected + - Depth level + - Actor/timing + - Any explicit user-specified must-have items incorporated + +**Important**: Each `/speckit.checklist` command invocation creates a checklist file using short, descriptive names unless file already exists. This allows: + +- Multiple checklists of different types (e.g., `ux.md`, `test.md`, `security.md`) +- Simple, memorable filenames that indicate checklist purpose +- Easy identification and navigation in the `checklists/` folder + +To avoid clutter, use descriptive types and clean up obsolete checklists when done. + +## Example Checklist Types & Sample Items + +**UX Requirements Quality:** `ux.md` + +Sample items (testing the requirements, NOT the implementation): + +- "Are visual hierarchy requirements defined with measurable criteria? [Clarity, Spec §FR-1]" +- "Is the number and positioning of UI elements explicitly specified? [Completeness, Spec §FR-1]" +- "Are interaction state requirements (hover, focus, active) consistently defined? [Consistency]" +- "Are accessibility requirements specified for all interactive elements? [Coverage, Gap]" +- "Is fallback behavior defined when images fail to load? [Edge Case, Gap]" +- "Can 'prominent display' be objectively measured? [Measurability, Spec §FR-4]" + +**API Requirements Quality:** `api.md` + +Sample items: + +- "Are error response formats specified for all failure scenarios? [Completeness]" +- "Are rate limiting requirements quantified with specific thresholds? [Clarity]" +- "Are authentication requirements consistent across all endpoints? [Consistency]" +- "Are retry/timeout requirements defined for external dependencies? [Coverage, Gap]" +- "Is versioning strategy documented in requirements? [Gap]" + +**Performance Requirements Quality:** `performance.md` + +Sample items: + +- "Are performance requirements quantified with specific metrics? [Clarity]" +- "Are performance targets defined for all critical user journeys? [Coverage]" +- "Are performance requirements under different load conditions specified? [Completeness]" +- "Can performance requirements be objectively measured? [Measurability]" +- "Are degradation requirements defined for high-load scenarios? [Edge Case, Gap]" + +**Security Requirements Quality:** `security.md` + +Sample items: + +- "Are authentication requirements specified for all protected resources? [Coverage]" +- "Are data protection requirements defined for sensitive information? [Completeness]" +- "Is the threat model documented and requirements aligned to it? [Traceability]" +- "Are security requirements consistent with compliance obligations? [Consistency]" +- "Are security failure/breach response requirements defined? [Gap, Exception Flow]" + +## Anti-Examples: What NOT To Do + +**❌ WRONG - These test implementation, not requirements:** + +```markdown +- [ ] CHK001 - Verify landing page displays 3 episode cards [Spec §FR-001] +- [ ] CHK002 - Test hover states work correctly on desktop [Spec §FR-003] +- [ ] CHK003 - Confirm logo click navigates to home page [Spec §FR-010] +- [ ] CHK004 - Check that related episodes section shows 3-5 items [Spec §FR-005] +``` + +**✅ CORRECT - These test requirements quality:** + +```markdown +- [ ] CHK001 - Are the number and layout of featured episodes explicitly specified? [Completeness, Spec §FR-001] +- [ ] CHK002 - Are hover state requirements consistently defined for all interactive elements? [Consistency, Spec §FR-003] +- [ ] CHK003 - Are navigation requirements clear for all clickable brand elements? [Clarity, Spec §FR-010] +- [ ] CHK004 - Is the selection criteria for related episodes documented? [Gap, Spec §FR-005] +- [ ] CHK005 - Are loading state requirements defined for asynchronous episode data? [Gap] +- [ ] CHK006 - Can "visual hierarchy" requirements be objectively measured? [Measurability, Spec §FR-001] +``` + +**Key Differences:** + +- Wrong: Tests if the system works correctly +- Correct: Tests if the requirements are written correctly +- Wrong: Verification of behavior +- Correct: Validation of requirement quality +- Wrong: "Does it do X?" +- Correct: "Is X clearly specified?" diff --git a/.cursor/commands/speckit.clarify.md b/.cursor/commands/speckit.clarify.md new file mode 100644 index 0000000..6b28dae --- /dev/null +++ b/.cursor/commands/speckit.clarify.md @@ -0,0 +1,181 @@ +--- +description: Identify underspecified areas in the current feature spec by asking up to 5 highly targeted clarification questions and encoding answers back into the spec. +handoffs: + - label: Build Technical Plan + agent: speckit.plan + prompt: Create a plan for the spec. I am building with... +--- + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Outline + +Goal: Detect and reduce ambiguity or missing decision points in the active feature specification and record the clarifications directly in the spec file. + +Note: This clarification workflow is expected to run (and be completed) BEFORE invoking `/speckit.plan`. If the user explicitly states they are skipping clarification (e.g., exploratory spike), you may proceed, but must warn that downstream rework risk increases. + +Execution steps: + +1. Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields: + - `FEATURE_DIR` + - `FEATURE_SPEC` + - (Optionally capture `IMPL_PLAN`, `TASKS` for future chained flows.) + - If JSON parsing fails, abort and instruct user to re-run `/speckit.specify` or verify feature branch environment. + - For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). + +2. Load the current spec file. Perform a structured ambiguity & coverage scan using this taxonomy. For each category, mark status: Clear / Partial / Missing. Produce an internal coverage map used for prioritization (do not output raw map unless no questions will be asked). + + Functional Scope & Behavior: + - Core user goals & success criteria + - Explicit out-of-scope declarations + - User roles / personas differentiation + + Domain & Data Model: + - Entities, attributes, relationships + - Identity & uniqueness rules + - Lifecycle/state transitions + - Data volume / scale assumptions + + Interaction & UX Flow: + - Critical user journeys / sequences + - Error/empty/loading states + - Accessibility or localization notes + + Non-Functional Quality Attributes: + - Performance (latency, throughput targets) + - Scalability (horizontal/vertical, limits) + - Reliability & availability (uptime, recovery expectations) + - Observability (logging, metrics, tracing signals) + - Security & privacy (authN/Z, data protection, threat assumptions) + - Compliance / regulatory constraints (if any) + + Integration & External Dependencies: + - External services/APIs and failure modes + - Data import/export formats + - Protocol/versioning assumptions + + Edge Cases & Failure Handling: + - Negative scenarios + - Rate limiting / throttling + - Conflict resolution (e.g., concurrent edits) + + Constraints & Tradeoffs: + - Technical constraints (language, storage, hosting) + - Explicit tradeoffs or rejected alternatives + + Terminology & Consistency: + - Canonical glossary terms + - Avoided synonyms / deprecated terms + + Completion Signals: + - Acceptance criteria testability + - Measurable Definition of Done style indicators + + Misc / Placeholders: + - TODO markers / unresolved decisions + - Ambiguous adjectives ("robust", "intuitive") lacking quantification + + For each category with Partial or Missing status, add a candidate question opportunity unless: + - Clarification would not materially change implementation or validation strategy + - Information is better deferred to planning phase (note internally) + +3. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints: + - Maximum of 10 total questions across the whole session. + - Each question must be answerable with EITHER: + - A short multiple‑choice selection (2–5 distinct, mutually exclusive options), OR + - A one-word / short‑phrase answer (explicitly constrain: "Answer in <=5 words"). + - Only include questions whose answers materially impact architecture, data modeling, task decomposition, test design, UX behavior, operational readiness, or compliance validation. + - Ensure category coverage balance: attempt to cover the highest impact unresolved categories first; avoid asking two low-impact questions when a single high-impact area (e.g., security posture) is unresolved. + - Exclude questions already answered, trivial stylistic preferences, or plan-level execution details (unless blocking correctness). + - Favor clarifications that reduce downstream rework risk or prevent misaligned acceptance tests. + - If more than 5 categories remain unresolved, select the top 5 by (Impact * Uncertainty) heuristic. + +4. Sequential questioning loop (interactive): + - Present EXACTLY ONE question at a time. + - For multiple‑choice questions: + - **Analyze all options** and determine the **most suitable option** based on: + - Best practices for the project type + - Common patterns in similar implementations + - Risk reduction (security, performance, maintainability) + - Alignment with any explicit project goals or constraints visible in the spec + - Present your **recommended option prominently** at the top with clear reasoning (1-2 sentences explaining why this is the best choice). + - Format as: `**Recommended:** Option [X] - ` + - Then render all options as a Markdown table: + + | Option | Description | + |--------|-------------| + | A |

Registration Form' + mock_get.return_value = mock_response + + response = requests.get(self.endpoint) + + self.assertEqual(response.status_code, 200) + self.assertIn('text/html', response.headers.get('Content-Type', '')) + + @patch('requests.get') + def test_registration_page_contains_form(self, mock_get): + """Test that registration page contains form elements""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = ''' + + +
+ + + + +
+ + + ''' + mock_get.return_value = mock_response + + response = requests.get(self.endpoint) + + self.assertIn('username', response.text) + self.assertIn('password', response.text) + + +class TestRegistrationProcess(unittest.TestCase): + """Tests for POST /registration endpoint""" + + def setUp(self): + self.base_url = TestLoginMicroserviceConfig.BASE_URL + self.endpoint = f"{self.base_url}/registration" + + @patch('requests.post') + def test_registration_success_redirect(self, mock_post): + """Test successful registration redirects to login""" + mock_response = MagicMock() + mock_response.status_code = 302 + mock_response.headers = {'Location': '/login'} + mock_post.return_value = mock_response + + response = requests.post( + self.endpoint, + data={ + 'username': 'newuser', + 'password': 'securePassword123', + 'passwordConfirm': 'securePassword123' + } + ) + + self.assertEqual(response.status_code, 302) + + @patch('requests.post') + def test_registration_with_valid_data(self, mock_post): + """Test registration with valid form data""" + mock_response = MagicMock() + mock_response.status_code = 302 + mock_post.return_value = mock_response + + form_data = { + 'username': 'johndoe', + 'password': 'securePassword123', + 'passwordConfirm': 'securePassword123' + } + response = requests.post(self.endpoint, data=form_data) + + self.assertIn(response.status_code, [200, 302]) + + @patch('requests.post') + def test_registration_password_mismatch(self, mock_post): + """Test registration fails when passwords don't match""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = 'Passwords do not match' + mock_post.return_value = mock_response + + form_data = { + 'username': 'johndoe', + 'password': 'password123', + 'passwordConfirm': 'differentPassword' + } + response = requests.post(self.endpoint, data=form_data) + + # Should return form with errors (200) not redirect (302) + self.assertEqual(response.status_code, 200) + + @patch('requests.post') + def test_registration_username_too_short(self, mock_post): + """Test registration fails with short username""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = 'Username too short' + mock_post.return_value = mock_response + + form_data = { + 'username': 'ab', # Less than 3 characters + 'password': 'securePassword123', + 'passwordConfirm': 'securePassword123' + } + response = requests.post(self.endpoint, data=form_data) + + self.assertEqual(response.status_code, 200) + + @patch('requests.post') + def test_registration_password_too_short(self, mock_post): + """Test registration fails with short password""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = 'Password too short' + mock_post.return_value = mock_response + + form_data = { + 'username': 'johndoe', + 'password': 'short', # Less than 8 characters + 'passwordConfirm': 'short' + } + response = requests.post(self.endpoint, data=form_data) + + self.assertEqual(response.status_code, 200) + + @patch('requests.post') + def test_registration_duplicate_username(self, mock_post): + """Test registration fails with existing username""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = 'Username already exists' + mock_post.return_value = mock_response + + form_data = { + 'username': 'existinguser', + 'password': 'securePassword123', + 'passwordConfirm': 'securePassword123' + } + response = requests.post(self.endpoint, data=form_data) + + self.assertEqual(response.status_code, 200) + + +class TestLoginPage(unittest.TestCase): + """Tests for GET /login endpoint""" + + def setUp(self): + self.base_url = TestLoginMicroserviceConfig.BASE_URL + self.endpoint = f"{self.base_url}/login" + + @patch('requests.get') + def test_get_login_page_success(self, mock_get): + """Test successfully retrieving login page""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {'Content-Type': 'text/html'} + mock_response.text = '
Login Form
' + mock_get.return_value = mock_response + + response = requests.get(self.endpoint) + + self.assertEqual(response.status_code, 200) + + @patch('requests.get') + def test_login_page_with_error_param(self, mock_get): + """Test login page shows error message when error param present""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = 'Invalid credentials' + mock_get.return_value = mock_response + + response = requests.get(self.endpoint, params={'error': ''}) + + self.assertEqual(response.status_code, 200) + + @patch('requests.get') + def test_login_page_with_logout_param(self, mock_get): + """Test login page shows logout message when logout param present""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = 'You have been logged out' + mock_get.return_value = mock_response + + response = requests.get(self.endpoint, params={'logout': ''}) + + self.assertEqual(response.status_code, 200) + + @patch('requests.get') + def test_login_page_contains_form(self, mock_get): + """Test that login page contains login form elements""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = ''' + + +
+ + + +
+ + + ''' + mock_get.return_value = mock_response + + response = requests.get(self.endpoint) + + self.assertIn('username', response.text) + self.assertIn('password', response.text) + + +class TestWelcomeRedirect(unittest.TestCase): + """Tests for GET / and GET /welcome endpoints""" + + def setUp(self): + self.base_url = TestLoginMicroserviceConfig.BASE_URL + + @patch('requests.get') + def test_root_redirects_to_app(self, mock_get): + """Test that root path redirects to main application""" + mock_response = MagicMock() + mock_response.status_code = 302 + mock_response.headers = {'Location': 'http://localhost:8080'} + mock_get.return_value = mock_response + + response = requests.get(f"{self.base_url}/") + + self.assertEqual(response.status_code, 302) + + @patch('requests.get') + def test_welcome_redirects_to_app(self, mock_get): + """Test that /welcome path redirects to main application""" + mock_response = MagicMock() + mock_response.status_code = 302 + mock_response.headers = {'Location': 'http://localhost:8080'} + mock_get.return_value = mock_response + + response = requests.get(f"{self.base_url}/welcome") + + self.assertEqual(response.status_code, 302) + + +class TestUserRegistrationFormSchema(unittest.TestCase): + """Tests for UserRegistrationForm schema validation""" + + @patch('requests.post') + def test_registration_form_has_required_fields(self, mock_post): + """Test that registration accepts all required fields""" + mock_response = MagicMock() + mock_response.status_code = 302 + mock_post.return_value = mock_response + + # All required fields per schema + form_data = { + 'username': 'testuser', + 'password': 'testpassword123', + 'passwordConfirm': 'testpassword123' + } + response = requests.post( + f"{TestLoginMicroserviceConfig.BASE_URL}/registration", + data=form_data + ) + + call_args = mock_post.call_args + submitted_data = call_args.kwargs.get('data', {}) + self.assertIn('username', submitted_data) + self.assertIn('password', submitted_data) + self.assertIn('passwordConfirm', submitted_data) + + @patch('requests.post') + def test_registration_username_length_validation(self, mock_post): + """Test username length constraints (3-32 characters)""" + mock_response = MagicMock() + mock_response.status_code = 302 + mock_post.return_value = mock_response + + # Valid username (within 3-32 chars) + form_data = { + 'username': 'validuser', # 9 characters + 'password': 'testpassword123', + 'passwordConfirm': 'testpassword123' + } + response = requests.post( + f"{TestLoginMicroserviceConfig.BASE_URL}/registration", + data=form_data + ) + + # Should succeed with valid length + self.assertIn(response.status_code, [200, 302]) + + +if __name__ == '__main__': + unittest.main() diff --git a/parity-tests/products-microservice-tests/requirements.txt b/parity-tests/products-microservice-tests/requirements.txt new file mode 100644 index 0000000..020ba71 --- /dev/null +++ b/parity-tests/products-microservice-tests/requirements.txt @@ -0,0 +1,4 @@ +requests>=2.28.0 +pytest>=7.0.0 +pytest-cov>=4.0.0 +responses>=0.22.0 diff --git a/parity-tests/products-microservice-tests/test_products_microservice.py b/parity-tests/products-microservice-tests/test_products_microservice.py new file mode 100644 index 0000000..0b73ce3 --- /dev/null +++ b/parity-tests/products-microservice-tests/test_products_microservice.py @@ -0,0 +1,443 @@ +""" +Unit tests for Products Microservice API + +Tests cover all endpoints defined in openapi/products-microservice.yaml: +- GET /products-microservice/product/{asin} +- GET /products-microservice/products +- GET /products-microservice/products/category/{category} +""" + +import unittest +from unittest.mock import patch, MagicMock +import requests +import json + + +class TestProductsMicroserviceConfig: + """Configuration for Products Microservice tests""" + BASE_URL = "http://localhost:8082" + PRODUCTS_BASE = f"{BASE_URL}/products-microservice" + + +class TestGetProductDetails(unittest.TestCase): + """Tests for GET /products-microservice/product/{asin} endpoint""" + + def setUp(self): + self.base_url = TestProductsMicroserviceConfig.PRODUCTS_BASE + self.endpoint = f"{self.base_url}/product" + + @patch('requests.get') + def test_get_product_details_success(self, mock_get): + """Test successfully retrieving product details""" + expected_product = { + "id": "B00BKQT2OI", + "brand": "Penguin Books", + "categories": ["Books", "Fiction"], + "imUrl": "https://images-na.ssl-images-amazon.com/images/I/51example.jpg", + "price": 14.99, + "title": "The Great Gatsby", + "description": "A novel by F. Scott Fitzgerald", + "also_bought": ["B00BKQT3XT"], + "also_viewed": ["B00BKQT4YU"], + "bought_together": [], + "buy_after_viewing": [], + "num_reviews": 1250, + "num_stars": 4875.5, + "avg_stars": 3.9 + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_product + mock_get.return_value = mock_response + + response = requests.get(f"{self.endpoint}/B00BKQT2OI") + + self.assertEqual(response.status_code, 200) + product = response.json() + self.assertEqual(product["id"], "B00BKQT2OI") + self.assertEqual(product["title"], "The Great Gatsby") + + @patch('requests.get') + def test_get_product_details_has_required_fields(self, mock_get): + """Test that product response contains required fields""" + expected_product = { + "id": "B00BKQT2OI", + "title": "The Great Gatsby", + "price": 14.99 + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_product + mock_get.return_value = mock_response + + response = requests.get(f"{self.endpoint}/B00BKQT2OI") + + product = response.json() + self.assertIn("id", product) + self.assertIn("title", product) + self.assertIn("price", product) + + @patch('requests.get') + def test_get_product_details_not_found(self, mock_get): + """Test getting a non-existent product""" + mock_response = MagicMock() + mock_response.status_code = 404 + mock_get.return_value = mock_response + + response = requests.get(f"{self.endpoint}/NONEXISTENT") + + self.assertEqual(response.status_code, 404) + + @patch('requests.get') + def test_get_product_price_is_numeric(self, mock_get): + """Test that product price is a number""" + expected_product = { + "id": "B00BKQT2OI", + "title": "The Great Gatsby", + "price": 14.99 + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_product + mock_get.return_value = mock_response + + response = requests.get(f"{self.endpoint}/B00BKQT2OI") + + product = response.json() + self.assertIsInstance(product["price"], (int, float)) + self.assertGreaterEqual(product["price"], 0) + + @patch('requests.get') + def test_get_product_with_recommendations(self, mock_get): + """Test product with recommendation arrays""" + expected_product = { + "id": "B00BKQT2OI", + "title": "The Great Gatsby", + "price": 14.99, + "also_bought": ["B001", "B002", "B003"], + "also_viewed": ["B004"], + "bought_together": ["B005"], + "buy_after_viewing": ["B006"] + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_product + mock_get.return_value = mock_response + + response = requests.get(f"{self.endpoint}/B00BKQT2OI") + + product = response.json() + self.assertIsInstance(product.get("also_bought", []), list) + self.assertIsInstance(product.get("also_viewed", []), list) + + @patch('requests.get') + def test_get_product_rating_fields(self, mock_get): + """Test product rating fields are present and valid""" + expected_product = { + "id": "B00BKQT2OI", + "title": "The Great Gatsby", + "price": 14.99, + "num_reviews": 1250, + "num_stars": 4875.5, + "avg_stars": 3.9 + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_product + mock_get.return_value = mock_response + + response = requests.get(f"{self.endpoint}/B00BKQT2OI") + + product = response.json() + self.assertIsInstance(product.get("num_reviews"), int) + self.assertIsInstance(product.get("avg_stars"), (int, float)) + self.assertGreaterEqual(product.get("avg_stars", 0), 0) + self.assertLessEqual(product.get("avg_stars", 0), 5) + + +class TestGetAllProducts(unittest.TestCase): + """Tests for GET /products-microservice/products endpoint""" + + def setUp(self): + self.base_url = TestProductsMicroserviceConfig.PRODUCTS_BASE + self.endpoint = f"{self.base_url}/products" + + @patch('requests.get') + def test_get_products_success(self, mock_get): + """Test successfully retrieving product list""" + expected_products = [ + {"id": "B001", "title": "Product 1", "price": 9.99}, + {"id": "B002", "title": "Product 2", "price": 19.99} + ] + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_products + mock_get.return_value = mock_response + + response = requests.get( + self.endpoint, + params={"limit": 12, "offset": 0} + ) + + self.assertEqual(response.status_code, 200) + products = response.json() + self.assertIsInstance(products, list) + + @patch('requests.get') + def test_get_products_with_pagination(self, mock_get): + """Test product list pagination""" + expected_products = [{"id": f"B{i:03d}", "title": f"Product {i}", "price": 9.99} + for i in range(12)] + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_products + mock_get.return_value = mock_response + + response = requests.get( + self.endpoint, + params={"limit": 12, "offset": 0} + ) + + products = response.json() + self.assertLessEqual(len(products), 12) + + @patch('requests.get') + def test_get_products_with_offset(self, mock_get): + """Test product list with offset for pagination""" + expected_products = [{"id": f"B{i:03d}", "title": f"Product {i}", "price": 9.99} + for i in range(12, 24)] + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_products + mock_get.return_value = mock_response + + response = requests.get( + self.endpoint, + params={"limit": 12, "offset": 12} + ) + + self.assertEqual(response.status_code, 200) + call_args = mock_get.call_args + params = call_args.kwargs.get("params", {}) + self.assertEqual(params.get("offset"), 12) + + @patch('requests.get') + def test_get_products_empty_result(self, mock_get): + """Test getting products when none exist""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = [] + mock_get.return_value = mock_response + + response = requests.get( + self.endpoint, + params={"limit": 12, "offset": 1000} + ) + + self.assertEqual(response.status_code, 200) + products = response.json() + self.assertEqual(products, []) + + @patch('requests.get') + def test_get_products_limit_parameter(self, mock_get): + """Test that limit parameter is respected""" + expected_products = [{"id": "B001", "title": "Product 1", "price": 9.99}] + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_products + mock_get.return_value = mock_response + + response = requests.get( + self.endpoint, + params={"limit": 1, "offset": 0} + ) + + products = response.json() + self.assertLessEqual(len(products), 1) + + +class TestGetProductsByCategory(unittest.TestCase): + """Tests for GET /products-microservice/products/category/{category} endpoint""" + + def setUp(self): + self.base_url = TestProductsMicroserviceConfig.PRODUCTS_BASE + self.endpoint = f"{self.base_url}/products/category" + + @patch('requests.get') + def test_get_products_by_category_success(self, mock_get): + """Test successfully retrieving products by category""" + expected_products = [ + { + "id": {"asin": "B001", "category": "Books"}, + "salesRank": 1, + "title": "Book 1", + "price": 14.99, + "imUrl": "https://example.com/img1.jpg", + "num_reviews": 100, + "num_stars": 450.0, + "avg_stars": 4.5 + } + ] + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_products + mock_get.return_value = mock_response + + response = requests.get( + f"{self.endpoint}/Books", + params={"limit": 12, "offset": 0} + ) + + self.assertEqual(response.status_code, 200) + products = response.json() + self.assertIsInstance(products, list) + + @patch('requests.get') + def test_get_products_by_category_books(self, mock_get): + """Test getting products in Books category""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = [] + mock_get.return_value = mock_response + + response = requests.get( + f"{self.endpoint}/Books", + params={"limit": 12, "offset": 0} + ) + + self.assertEqual(response.status_code, 200) + + @patch('requests.get') + def test_get_products_by_category_music(self, mock_get): + """Test getting products in Music category""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = [] + mock_get.return_value = mock_response + + response = requests.get( + f"{self.endpoint}/Music", + params={"limit": 12, "offset": 0} + ) + + self.assertEqual(response.status_code, 200) + + @patch('requests.get') + def test_get_products_by_category_beauty(self, mock_get): + """Test getting products in Beauty category""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = [] + mock_get.return_value = mock_response + + response = requests.get( + f"{self.endpoint}/Beauty", + params={"limit": 12, "offset": 0} + ) + + self.assertEqual(response.status_code, 200) + + @patch('requests.get') + def test_get_products_by_category_electronics(self, mock_get): + """Test getting products in Electronics category""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = [] + mock_get.return_value = mock_response + + response = requests.get( + f"{self.endpoint}/Electronics", + params={"limit": 12, "offset": 0} + ) + + self.assertEqual(response.status_code, 200) + + @patch('requests.get') + def test_get_products_category_has_ranking_info(self, mock_get): + """Test that category products include ranking information""" + expected_products = [ + { + "id": {"asin": "B001", "category": "Books"}, + "salesRank": 1, + "title": "Book 1", + "price": 14.99, + "avg_stars": 4.5 + } + ] + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_products + mock_get.return_value = mock_response + + response = requests.get( + f"{self.endpoint}/Books", + params={"limit": 12, "offset": 0} + ) + + products = response.json() + if products: + self.assertIn("salesRank", products[0]) + self.assertIn("id", products[0]) + + @patch('requests.get') + def test_get_products_category_with_special_characters(self, mock_get): + """Test category with special characters (URL encoded)""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = [] + mock_get.return_value = mock_response + + # Categories like "Kitchen & Dining" need URL encoding + response = requests.get( + f"{self.endpoint}/Kitchen%20%26%20Dining", + params={"limit": 12, "offset": 0} + ) + + self.assertEqual(response.status_code, 200) + + +class TestProductMetadataSchema(unittest.TestCase): + """Tests for ProductMetadata schema validation""" + + @patch('requests.get') + def test_product_metadata_complete_schema(self, mock_get): + """Test complete ProductMetadata schema""" + complete_product = { + "id": "B00BKQT2OI", + "brand": "Penguin Books", + "categories": ["Books", "Fiction", "Literature"], + "imUrl": "https://images-na.ssl-images-amazon.com/images/I/51example.jpg", + "price": 14.99, + "title": "The Great Gatsby", + "description": "A novel written by American author F. Scott Fitzgerald...", + "also_bought": ["B00BKQT3XT", "B00BKQT4YU"], + "also_viewed": ["B00BKQT5ZV"], + "bought_together": ["B00BKQT6AW"], + "buy_after_viewing": ["B00BKQT7BX"], + "num_reviews": 1250, + "num_stars": 4875.5, + "avg_stars": 3.9 + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = complete_product + mock_get.return_value = mock_response + + response = requests.get( + f"{TestProductsMicroserviceConfig.PRODUCTS_BASE}/product/B00BKQT2OI" + ) + + product = response.json() + + # Validate all expected fields are present + expected_fields = [ + "id", "brand", "categories", "imUrl", "price", "title", + "description", "also_bought", "also_viewed", "bought_together", + "buy_after_viewing", "num_reviews", "num_stars", "avg_stars" + ] + for field in expected_fields: + self.assertIn(field, product) + + +if __name__ == '__main__': + unittest.main() diff --git a/parity-tests/react-ui-bff-tests/requirements.txt b/parity-tests/react-ui-bff-tests/requirements.txt new file mode 100644 index 0000000..020ba71 --- /dev/null +++ b/parity-tests/react-ui-bff-tests/requirements.txt @@ -0,0 +1,4 @@ +requests>=2.28.0 +pytest>=7.0.0 +pytest-cov>=4.0.0 +responses>=0.22.0 diff --git a/parity-tests/react-ui-bff-tests/test_react_ui_bff.py b/parity-tests/react-ui-bff-tests/test_react_ui_bff.py new file mode 100644 index 0000000..dc043e0 --- /dev/null +++ b/parity-tests/react-ui-bff-tests/test_react_ui_bff.py @@ -0,0 +1,594 @@ +""" +Unit tests for React UI Backend-for-Frontend (BFF) API + +Tests cover all endpoints defined in openapi/react-ui-bff.yaml: +- GET /api/hello - Health check endpoint +- GET /products - Get homepage products +- GET /products/category/{category} - Get products by category +- GET /products/details - Get product details +- POST /cart/add - Add product to cart +- POST /cart/get - Get cart contents +- POST /cart/getCart - Get cart contents (alternate) +- POST /cart/remove - Remove product from cart +- POST /cart/checkout - Process checkout +""" + +import unittest +from unittest.mock import patch, MagicMock +import requests +import json + + +class TestReactUiBffConfig: + """Configuration for React UI BFF tests""" + BASE_URL = "http://localhost:8080" + + +class TestHealthCheck(unittest.TestCase): + """Tests for GET /api/hello endpoint""" + + def setUp(self): + self.base_url = TestReactUiBffConfig.BASE_URL + self.endpoint = f"{self.base_url}/api/hello" + + @patch('requests.get') + def test_health_check_success(self, mock_get): + """Test health check returns server time""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = "Hello, the time at the server is now Mon Jan 15 10:30:00 EST 2024" + mock_get.return_value = mock_response + + response = requests.get(self.endpoint) + + self.assertEqual(response.status_code, 200) + self.assertIn("Hello", response.text) + + @patch('requests.get') + def test_health_check_contains_time(self, mock_get): + """Test health check response contains time information""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = "Hello, the time at the server is now Mon Jan 15 10:30:00 EST 2024" + mock_get.return_value = mock_response + + response = requests.get(self.endpoint) + + self.assertIn("time", response.text.lower()) + + +class TestGetHomepageProducts(unittest.TestCase): + """Tests for GET /products endpoint""" + + def setUp(self): + self.base_url = TestReactUiBffConfig.BASE_URL + self.endpoint = f"{self.base_url}/products" + + @patch('requests.get') + def test_get_products_success(self, mock_get): + """Test successfully retrieving homepage products""" + expected_products = json.dumps([ + {"id": "B001", "title": "Product 1", "price": 9.99}, + {"id": "B002", "title": "Product 2", "price": 19.99} + ]) + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = expected_products + mock_response.json.return_value = json.loads(expected_products) + mock_get.return_value = mock_response + + response = requests.get(self.endpoint) + + self.assertEqual(response.status_code, 200) + + @patch('requests.get') + def test_get_products_returns_list(self, mock_get): + """Test that products endpoint returns a list""" + expected_products = [{"id": "B001", "title": "Product 1"}] + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_products + mock_get.return_value = mock_response + + response = requests.get(self.endpoint) + + products = response.json() + self.assertIsInstance(products, list) + + @patch('requests.get') + def test_get_products_default_limit(self, mock_get): + """Test that homepage returns default number of products (10)""" + expected_products = [{"id": f"B{i:03d}"} for i in range(10)] + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_products + mock_get.return_value = mock_response + + response = requests.get(self.endpoint) + + products = response.json() + self.assertLessEqual(len(products), 10) + + +class TestGetProductsByCategory(unittest.TestCase): + """Tests for GET /products/category/{category} endpoint""" + + def setUp(self): + self.base_url = TestReactUiBffConfig.BASE_URL + self.endpoint = f"{self.base_url}/products/category" + + @patch('requests.get') + def test_get_products_by_category_success(self, mock_get): + """Test successfully retrieving products by category""" + expected_products = [ + {"id": {"asin": "B001", "category": "Books"}, "title": "Book 1"} + ] + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_products + mock_get.return_value = mock_response + + response = requests.get( + f"{self.endpoint}/Books", + params={"limit": 12, "offset": 0} + ) + + self.assertEqual(response.status_code, 200) + + @patch('requests.get') + def test_get_products_by_category_with_pagination(self, mock_get): + """Test category products with pagination parameters""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = [] + mock_get.return_value = mock_response + + response = requests.get( + f"{self.endpoint}/Books", + params={"limit": 12, "offset": 24} + ) + + call_args = mock_get.call_args + params = call_args.kwargs.get("params", {}) + self.assertEqual(params.get("limit"), 12) + self.assertEqual(params.get("offset"), 24) + + @patch('requests.get') + def test_get_products_various_categories(self, mock_get): + """Test fetching from various categories""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = [] + mock_get.return_value = mock_response + + categories = ["Books", "Music", "Electronics", "Beauty"] + for category in categories: + response = requests.get( + f"{self.endpoint}/{category}", + params={"limit": 12, "offset": 0} + ) + self.assertEqual(response.status_code, 200) + + +class TestGetProductDetails(unittest.TestCase): + """Tests for GET /products/details endpoint""" + + def setUp(self): + self.base_url = TestReactUiBffConfig.BASE_URL + self.endpoint = f"{self.base_url}/products/details" + + @patch('requests.get') + def test_get_product_details_success(self, mock_get): + """Test successfully retrieving product details""" + expected_product = { + "id": "B00BKQT2OI", + "title": "The Great Gatsby", + "price": 14.99, + "brand": "Penguin Books" + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_product + mock_get.return_value = mock_response + + response = requests.get( + self.endpoint, + params={"asin": "B00BKQT2OI"} + ) + + self.assertEqual(response.status_code, 200) + product = response.json() + self.assertEqual(product["id"], "B00BKQT2OI") + + @patch('requests.get') + def test_get_product_details_requires_asin(self, mock_get): + """Test that ASIN parameter is required""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"id": "B00TEST"} + mock_get.return_value = mock_response + + response = requests.get( + self.endpoint, + params={"asin": "B00TEST"} + ) + + call_args = mock_get.call_args + params = call_args.kwargs.get("params", {}) + self.assertIn("asin", params) + + @patch('requests.get') + def test_get_product_details_full_metadata(self, mock_get): + """Test that product details includes full metadata""" + expected_product = { + "id": "B00BKQT2OI", + "title": "The Great Gatsby", + "price": 14.99, + "brand": "Penguin Books", + "categories": ["Books", "Fiction"], + "imUrl": "https://example.com/image.jpg", + "description": "A novel", + "also_bought": ["B001"], + "num_reviews": 1250, + "avg_stars": 3.9 + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_product + mock_get.return_value = mock_response + + response = requests.get( + self.endpoint, + params={"asin": "B00BKQT2OI"} + ) + + product = response.json() + self.assertIn("title", product) + self.assertIn("price", product) + + +class TestAddToCart(unittest.TestCase): + """Tests for POST /cart/add endpoint""" + + def setUp(self): + self.base_url = TestReactUiBffConfig.BASE_URL + self.endpoint = f"{self.base_url}/cart/add" + + @patch('requests.post') + def test_add_to_cart_success(self, mock_post): + """Test successfully adding product to cart""" + expected_cart = '{"B00BKQT2OI": 1}' + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = expected_cart + mock_response.json.return_value = {"B00BKQT2OI": 1} + mock_post.return_value = mock_response + + response = requests.post( + self.endpoint, + params={"asin": "B00BKQT2OI"} + ) + + self.assertEqual(response.status_code, 200) + + @patch('requests.post') + def test_add_to_cart_returns_updated_cart(self, mock_post): + """Test that adding product returns updated cart contents""" + expected_cart = {"B00BKQT2OI": 2, "B00OTHER": 1} + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_cart + mock_post.return_value = mock_response + + response = requests.post( + self.endpoint, + params={"asin": "B00BKQT2OI"} + ) + + cart = response.json() + self.assertIsInstance(cart, dict) + + @patch('requests.post') + def test_add_to_cart_requires_asin(self, mock_post): + """Test that ASIN parameter is required""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"B00TEST": 1} + mock_post.return_value = mock_response + + response = requests.post( + self.endpoint, + params={"asin": "B00TEST"} + ) + + call_args = mock_post.call_args + params = call_args.kwargs.get("params", {}) + self.assertIn("asin", params) + + +class TestGetCart(unittest.TestCase): + """Tests for POST /cart/get endpoint""" + + def setUp(self): + self.base_url = TestReactUiBffConfig.BASE_URL + self.endpoint = f"{self.base_url}/cart/get" + + @patch('requests.post') + def test_get_cart_success(self, mock_post): + """Test successfully retrieving cart contents""" + expected_cart = {"B00BKQT2OI": 2, "B00BKQT3XT": 1} + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_cart + mock_post.return_value = mock_response + + response = requests.post(self.endpoint) + + self.assertEqual(response.status_code, 200) + cart = response.json() + self.assertIsInstance(cart, dict) + + @patch('requests.post') + def test_get_cart_empty(self, mock_post): + """Test retrieving empty cart""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {} + mock_post.return_value = mock_response + + response = requests.post(self.endpoint) + + cart = response.json() + self.assertEqual(cart, {}) + + @patch('requests.post') + def test_cart_format_asin_to_quantity(self, mock_post): + """Test cart format is ASIN -> quantity map""" + expected_cart = {"B00BKQT2OI": 2, "B00BKQT3XT": 1} + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_cart + mock_post.return_value = mock_response + + response = requests.post(self.endpoint) + + cart = response.json() + for asin, quantity in cart.items(): + self.assertIsInstance(asin, str) + self.assertIsInstance(quantity, int) + self.assertGreater(quantity, 0) + + +class TestGetCartAlternate(unittest.TestCase): + """Tests for POST /cart/getCart endpoint""" + + def setUp(self): + self.base_url = TestReactUiBffConfig.BASE_URL + self.endpoint = f"{self.base_url}/cart/getCart" + + @patch('requests.post') + def test_get_cart_alternate_success(self, mock_post): + """Test alternate get cart endpoint""" + expected_cart = {"B00BKQT2OI": 1} + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_cart + mock_post.return_value = mock_response + + response = requests.post(self.endpoint) + + self.assertEqual(response.status_code, 200) + + @patch('requests.post') + def test_get_cart_alternate_same_format(self, mock_post): + """Test that alternate endpoint returns same format as main""" + expected_cart = {"B00BKQT2OI": 2} + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_cart + mock_post.return_value = mock_response + + response = requests.post(self.endpoint) + + cart = response.json() + self.assertIsInstance(cart, dict) + + +class TestRemoveFromCart(unittest.TestCase): + """Tests for POST /cart/remove endpoint""" + + def setUp(self): + self.base_url = TestReactUiBffConfig.BASE_URL + self.endpoint = f"{self.base_url}/cart/remove" + + @patch('requests.post') + def test_remove_from_cart_success(self, mock_post): + """Test successfully removing product from cart""" + expected_cart = {} + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_cart + mock_post.return_value = mock_response + + response = requests.post( + self.endpoint, + params={"asin": "B00BKQT2OI"} + ) + + self.assertEqual(response.status_code, 200) + + @patch('requests.post') + def test_remove_from_cart_returns_updated_cart(self, mock_post): + """Test that removing returns updated cart""" + expected_cart = {"B00OTHER": 1} + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_cart + mock_post.return_value = mock_response + + response = requests.post( + self.endpoint, + params={"asin": "B00BKQT2OI"} + ) + + cart = response.json() + self.assertNotIn("B00BKQT2OI", cart) + + @patch('requests.post') + def test_remove_from_cart_requires_asin(self, mock_post): + """Test that ASIN parameter is required""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {} + mock_post.return_value = mock_response + + response = requests.post( + self.endpoint, + params={"asin": "B00TEST"} + ) + + call_args = mock_post.call_args + params = call_args.kwargs.get("params", {}) + self.assertIn("asin", params) + + +class TestCheckout(unittest.TestCase): + """Tests for POST /cart/checkout endpoint""" + + def setUp(self): + self.base_url = TestReactUiBffConfig.BASE_URL + self.endpoint = f"{self.base_url}/cart/checkout" + + @patch('requests.post') + def test_checkout_success(self, mock_post): + """Test successful checkout""" + expected_response = { + "status": "SUCCESS", + "orderNumber": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "orderDetails": "Customer bought these Items: Product: The Great Gatsby, Quantity: 2; Order Total is : 29.98" + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_response + mock_post.return_value = mock_response + + response = requests.post(self.endpoint) + + self.assertEqual(response.status_code, 200) + result = response.json() + self.assertEqual(result["status"], "SUCCESS") + + @patch('requests.post') + def test_checkout_failure(self, mock_post): + """Test checkout failure""" + expected_response = { + "status": "FAILURE", + "orderNumber": "", + "orderDetails": "Product is Out of Stock!" + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_response + mock_post.return_value = mock_response + + response = requests.post(self.endpoint) + + result = response.json() + self.assertEqual(result["status"], "FAILURE") + + @patch('requests.post') + def test_checkout_returns_order_details(self, mock_post): + """Test that checkout returns order details""" + expected_response = { + "status": "SUCCESS", + "orderNumber": "test-order-123", + "orderDetails": "Order completed successfully" + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_response + mock_post.return_value = mock_response + + response = requests.post(self.endpoint) + + result = response.json() + self.assertIn("orderDetails", result) + self.assertIn("orderNumber", result) + + @patch('requests.post') + def test_checkout_status_enum_values(self, mock_post): + """Test checkout status is valid enum value""" + expected_response = { + "status": "SUCCESS", + "orderNumber": "uuid", + "orderDetails": "details" + } + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = expected_response + mock_post.return_value = mock_response + + response = requests.post(self.endpoint) + + result = response.json() + self.assertIn(result["status"], ["SUCCESS", "FAILURE"]) + + +class TestCartWorkflow(unittest.TestCase): + """Integration-style tests for cart workflows through BFF""" + + def setUp(self): + self.base_url = TestReactUiBffConfig.BASE_URL + + @patch('requests.post') + @patch('requests.get') + def test_add_get_remove_checkout_workflow(self, mock_get, mock_post): + """Test complete shopping workflow""" + # Setup responses for workflow + add_response = MagicMock() + add_response.status_code = 200 + add_response.json.return_value = {"B00BKQT2OI": 1} + + get_response = MagicMock() + get_response.status_code = 200 + get_response.json.return_value = {"B00BKQT2OI": 1} + + remove_response = MagicMock() + remove_response.status_code = 200 + remove_response.json.return_value = {} + + checkout_response = MagicMock() + checkout_response.status_code = 200 + checkout_response.json.return_value = { + "status": "SUCCESS", + "orderNumber": "test-123", + "orderDetails": "Order completed" + } + + mock_post.side_effect = [add_response, get_response, remove_response, checkout_response] + + # Add product + response1 = requests.post( + f"{self.base_url}/cart/add", + params={"asin": "B00BKQT2OI"} + ) + self.assertEqual(response1.status_code, 200) + + # Get cart + response2 = requests.post(f"{self.base_url}/cart/get") + self.assertEqual(response2.status_code, 200) + + # Remove product + response3 = requests.post( + f"{self.base_url}/cart/remove", + params={"asin": "B00BKQT2OI"} + ) + self.assertEqual(response3.status_code, 200) + + # Checkout + response4 = requests.post(f"{self.base_url}/cart/checkout") + self.assertEqual(response4.status_code, 200) + + +if __name__ == '__main__': + unittest.main() diff --git a/parity-tests/run_all_tests.sh b/parity-tests/run_all_tests.sh new file mode 100755 index 0000000..83dcec5 --- /dev/null +++ b/parity-tests/run_all_tests.sh @@ -0,0 +1,228 @@ +#!/bin/bash +# +# Run all parity tests for Yugastore microservices +# +# This script discovers and runs all Python unit tests in the parity-tests subdirectories. +# It creates a virtual environment if needed, installs dependencies, and runs pytest. +# +# Usage: +# ./run_all_tests.sh # Run all tests +# ./run_all_tests.sh --verbose # Run with verbose output +# ./run_all_tests.sh --coverage # Run with coverage report +# ./run_all_tests.sh --service # Run tests for specific service only +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +VENV_DIR="${SCRIPT_DIR}/.venv" +VERBOSE="" +COVERAGE="" +SERVICE_FILTER="" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --verbose, -v Run tests with verbose output" + echo " --coverage, -c Generate coverage report" + echo " --service, -s NAME Run tests for specific service only" + echo " --help, -h Show this help message" + echo "" + echo "Available services:" + for dir in "${SCRIPT_DIR}"/*-tests; do + if [[ -d "$dir" ]]; then + echo " - $(basename "$dir" | sed 's/-tests$//')" + fi + done +} + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --verbose|-v) + VERBOSE="-v" + shift + ;; + --coverage|-c) + COVERAGE="--cov=. --cov-report=term-missing --cov-report=html:coverage_report" + shift + ;; + --service|-s) + SERVICE_FILTER="$2" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + log_error "Unknown option: $1" + usage + exit 1 + ;; + esac +done + +# Check for Python 3 +if command -v python3 &> /dev/null; then + PYTHON_CMD="python3" +elif command -v python &> /dev/null; then + PYTHON_CMD="python" +else + log_error "Python 3 is required but not found" + exit 1 +fi + +log_info "Using Python: $($PYTHON_CMD --version)" + +# Create virtual environment if it doesn't exist +if [[ ! -d "$VENV_DIR" ]]; then + log_info "Creating virtual environment..." + $PYTHON_CMD -m venv "$VENV_DIR" +fi + +# Activate virtual environment +log_info "Activating virtual environment..." +source "${VENV_DIR}/bin/activate" + +# Upgrade pip +pip install --quiet --upgrade pip + +# Collect all requirements and install +log_info "Installing dependencies..." +TEMP_REQUIREMENTS=$(mktemp) +cat "${SCRIPT_DIR}"/*/requirements.txt 2>/dev/null | sort -u > "$TEMP_REQUIREMENTS" +pip install --quiet -r "$TEMP_REQUIREMENTS" +rm "$TEMP_REQUIREMENTS" + +# Find test directories +TEST_DIRS=() +for dir in "${SCRIPT_DIR}"/*-tests; do + if [[ -d "$dir" ]]; then + if [[ -n "$SERVICE_FILTER" ]]; then + if [[ "$(basename "$dir")" == *"${SERVICE_FILTER}"* ]]; then + TEST_DIRS+=("$dir") + fi + else + TEST_DIRS+=("$dir") + fi + fi +done + +if [[ ${#TEST_DIRS[@]} -eq 0 ]]; then + log_error "No test directories found" + if [[ -n "$SERVICE_FILTER" ]]; then + log_error "No tests matching service filter: $SERVICE_FILTER" + fi + exit 1 +fi + +# Print test summary +echo "" +echo "========================================" +echo " Parity Tests Runner" +echo "========================================" +echo "" +log_info "Found ${#TEST_DIRS[@]} test suite(s):" +for dir in "${TEST_DIRS[@]}"; do + echo " - $(basename "$dir")" +done +echo "" + +# Run tests +FAILED_SUITES=() +PASSED_SUITES=() +TOTAL_TESTS=0 +TOTAL_PASSED=0 +TOTAL_FAILED=0 + +for test_dir in "${TEST_DIRS[@]}"; do + suite_name=$(basename "$test_dir") + echo "" + echo "----------------------------------------" + log_info "Running: ${suite_name}" + echo "----------------------------------------" + + cd "$test_dir" + + # Run pytest and capture result + set +e + if [[ -n "$COVERAGE" ]]; then + pytest $VERBOSE $COVERAGE . 2>&1 + else + pytest $VERBOSE . 2>&1 + fi + result=$? + set -e + + if [[ $result -eq 0 ]]; then + log_success "${suite_name} passed" + PASSED_SUITES+=("$suite_name") + else + log_error "${suite_name} failed" + FAILED_SUITES+=("$suite_name") + fi + + cd "$SCRIPT_DIR" +done + +# Print summary +echo "" +echo "========================================" +echo " Test Summary" +echo "========================================" +echo "" + +if [[ ${#PASSED_SUITES[@]} -gt 0 ]]; then + log_success "Passed suites (${#PASSED_SUITES[@]}):" + for suite in "${PASSED_SUITES[@]}"; do + echo " ✓ $suite" + done +fi + +if [[ ${#FAILED_SUITES[@]} -gt 0 ]]; then + echo "" + log_error "Failed suites (${#FAILED_SUITES[@]}):" + for suite in "${FAILED_SUITES[@]}"; do + echo " ✗ $suite" + done +fi + +echo "" +echo "----------------------------------------" +echo "Total: ${#TEST_DIRS[@]} suite(s), ${#PASSED_SUITES[@]} passed, ${#FAILED_SUITES[@]} failed" +echo "----------------------------------------" + +# Deactivate virtual environment +deactivate + +# Exit with appropriate code +if [[ ${#FAILED_SUITES[@]} -gt 0 ]]; then + exit 1 +fi + +exit 0 From dc0f40a61e9a36c534f48470cd439e43544e8f34 Mon Sep 17 00:00:00 2001 From: ironchef001 Date: Mon, 8 Dec 2025 20:55:07 -0500 Subject: [PATCH 09/13] Add comprehensive constitution for cloud-native e-commerce platform - Establish core architectural principles (cloud-native, microservices, API stability) - Define functional requirements (catalog, cart, checkout, authentication) - Set non-functional requirements (performance, scalability, reliability targets) - Document system architecture principles for all components - Specify SDLC workflow with AI-enabled, spec-driven development - Establish quality gates, observability, and security standards - Define documentation framework and governance requirements Version 1.0 ratified on 2025-12-08 --- .specify/memory/constitution.md | 359 ++++++++++++++++++++++++++++---- 1 file changed, 322 insertions(+), 37 deletions(-) diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index a4670ff..18fc16b 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -1,50 +1,335 @@ -# [PROJECT_NAME] Constitution - +# **📘 Constitution for the Cloud-Native E-Commerce Bookstore Platform** -## Core Principles +**Version 1.0 (Draft for AI-Enabled, Spec-Driven Development)** -### [PRINCIPLE_1_NAME] - -[PRINCIPLE_1_DESCRIPTION] - +--- -### [PRINCIPLE_2_NAME] - -[PRINCIPLE_2_DESCRIPTION] - +## **1. Purpose and Scope** -### [PRINCIPLE_3_NAME] - -[PRINCIPLE_3_DESCRIPTION] - +This constitution establishes the **foundational principles, design philosophy, engineering rules, governance, and SDLC workflow** for building and operating a **cloud-native, scalable, resilient microservices-based e-commerce platform**. +The system supports: -### [PRINCIPLE_4_NAME] - -[PRINCIPLE_4_DESCRIPTION] - +* Online bookstore shopping experience +* Product catalog and search +* Cart and checkout +* Order lifecycle & fulfillment +* Identity, authentication & authorization +* Payment routing (future/optional) +* Inventory and data storage +* API gateway & routing +* Observability, resilience, performance -### [PRINCIPLE_5_NAME] - -[PRINCIPLE_5_DESCRIPTION] - +This constitution is **technology-agnostic**, forward-looking, and supports **AI-assisted, spec-driven development workflows**, ensuring durable engineering discipline and predictable delivery. -## [SECTION_2_NAME] - +--- -[SECTION_2_CONTENT] - +## **2. Core Values & Architectural Principles** -## [SECTION_3_NAME] - +### **2.1 Cloud-Native First** -[SECTION_3_CONTENT] - +* All components must be designed for **horizontal scalability**, **stateless compute**, and **managed cloud services**. +* Prefer serverless or containerized workloads over self-managed infrastructure. +* Minimize operational burden by adopting managed services (databases, queues, identity providers). -## Governance - +### **2.2 Microservices with Clear Boundaries** -[GOVERNANCE_RULES] - +* Each service owns a **single, cohesive domain** (e.g., Catalog, Cart, Checkout, Authentication). +* Services communicate through **well-defined APIs**; avoid implicit dependencies. +* No shared mutable database across services. + +### **2.3 API Contract Stability** + +* RESTful APIs must have: + + * Explicit versioning + * Backward-compatible evolution + * Strong schema validation +* Breaking changes require proper version rollouts. + +### **2.4 Fail-Fast, Safe-to-Fail** + +* Services should detect invalid states early and fail gracefully. +* Degradation modes must protect user experience (e.g., cached catalog if upstream fails). + +### **2.5 Test-First, Test-Always** + +* No code is merged without automated tests. +* Unit, integration, contract, and load tests are required. +* APIs require machine-readable contracts enabling automated tests. + +### **2.6 Security & Privacy by Default** + +* Zero-trust architecture. +* Enforce authentication, authorization, input validation, and output encoding. +* No PII or sensitive customer data stored without encryption in transit and at rest. + +### **2.7 Observability as a First-Class Citizen** + +* Every service emits: + + * Structured logs + * Metrics + * Traces +* SLOs and dashboards must exist before production deployment. + +--- + +## **3. Functional Requirements (High-Level)** + +### **3.1 Product Catalog** + +* Browse/search books +* Detail pages with pricing, metadata, availability +* Support category filtering & pagination + +### **3.2 Shopping Cart** + +* Add/remove/update items +* Persist carts for authenticated users +* Stateless operations backed by durable storage + +### **3.3 Checkout & Order Processing** + +* Tax/shipping calculation +* Order summary +* Payment flow (future) +* Order creation & order tracking lifecycle + +### **3.4 User Authentication** + +* Support login/signup +* Token-based session management +* Integrate with cloud identity provider (Cognito/Okta/etc.) + +### **3.5 System Management APIs** + +* Health checks +* Service discovery +* Operational endpoints + +--- + +## **4. Non-Functional Requirements** + +### **4.1 Performance** + +* API response time target: ≤ 200 ms at P95 +* Catalog & cart endpoints must support burst traffic + +### **4.2 Scalability** + +* Horizontal scaling for compute and storage +* Services must avoid single points of failure + +### **4.3 Reliability** + +* SLO: 99.9% uptime +* Redundancy for critical services +* Graceful degradation & fallback strategies + +### **4.4 Resilience** + +* Circuit breakers, retries, backoff strategies +* Rate limiting and API quota enforcement + +### **4.5 Accessibility (for UI)** + +* WCAG AA compliance +* Keyboard navigation support +* Alternative text for images + +### **4.6 Internationalization (future)** + +* Multi-language content +* Configurable currency and locale formats + +--- + +## **5. System Architecture Principles** + +### **5.1 Front-End (React or modern framework)** + +* Modular components +* API-driven UI +* Progressive rendering & performance optimization +* Strong and consistent UX patterns +* Do not embed business logic in the UI layer + +### **5.2 API Gateway** + +* Single entry point for all API consumers +* Handles routing, rate limiting, request validation +* Should enforce authentication before forwarding requests + +### **5.3 Service Discovery** + +* Each service must register itself +* Health checks determine routing eligibility +* Prefer managed service discovery (cloud-native) + +### **5.4 Backend Microservices** + +* Stateless compute +* Clear ownership of business capability +* Storage abstraction: repository/persistence layer +* Domain models with explicit boundaries + +### **5.5 Data Storage** + +* Based on domain needs: SQL or NoSQL +* Services own their data +* No cross-service shared tables +* Include migration/versioning strategy + +--- + +## **6. SDLC & AI-Enabled Engineering Workflow** + +### **6.1 Spec-Driven Development** + +All code development begins with: + +1. **Business Requirements** +2. **Specification (`docs/specs/*.md`)** +3. **Plan and tasks (`docs/plan.md`, `docs/tasks.md`)** +4. **AI-assisted review and refinement** + +No coding starts without an approved spec. + +### **6.2 AI-Assisted Engineering (LLM + Spec-Driven)** + +LLM participation includes: + +* Refining specs +* Generating architecture diagrams +* Producing tasks and subtasks +* Generating boilerplate code +* Maintaining constitution compliance +* Ensuring alignment with governance + +Developers must review all generated outputs before merging. + +### **6.3 Governance Requirements** + +* Architecture decisions logged as ADRs +* No undocumented design deviations +* Constitution must guide every service and artifact + +### **6.4 Branching & Versioning** + +``` +feature/US001-short-description +feature/US002-short-description +``` + +* Semantic versioning for all APIs +* Patch → bug fixes +* Minor → backward-compatible enhancements +* Major → breaking changes + +### **6.5 CI/CD Requirements** + +* Automated lint, test, security, dependency scanning +* Branch Protection rules enforced +* Zero manual deployments to production + +--- + +## **7. Quality Gates** + +Every merge request must include: + +* Meaningful description +* Trace to user story +* Unit tests & integration tests +* API contract documentation +* Updated architecture docs when needed + +No exceptions. + +--- + +## **8. Observability & Operations** + +### **8.1 Required Telemetry** + +* Logs → structured JSON +* Metrics → latency, error rate, throughput +* Traces → service-to-service spans + +### **8.2 Error Handling & Monitoring** + +* Centralized logging +* Alerts for SLO violations +* Dead-letter queues for async failures + +--- + +## **9. Security & Compliance** + +* Validate all inputs +* Sanitize outputs +* Never trust client-side data +* Enforce least-privilege IAM + +Data Protection: + +* TLS 1.2+ +* Encryption at rest +* Regular key rotation +* Sensitive data masking + +--- + +## **10. Documentation Framework** + +### **10.1 docs/inputs/** + +Raw business requirements, diagrams, meeting notes. + +### **10.2 docs/specs/** + +AI-refined specifications (single source of truth). + +### **10.3 docs/plan/** + +High-level delivery strategy. + +### **10.4 docs/tasks/** + +User stories + task breakdown for implementation. + +### **10.5 docs/adr/** + +Architectural decisions and rationale. + +### **10.6 constitution.md** + +This file—governing everything. + +--- + +## **11. Compliance with This Constitution** + +All contributors—human and AI—must adhere to the constitution. +Deviations must be explicitly approved through an ADR process. + +--- + +## **12. Future Evolution** + +This constitution will evolve as: + +* Architecture matures +* Business requirements expand +* AI capabilities increase +* Engineering practices improve + +Revisions follow semantic versioning (e.g., 1.1, 2.0). + +--- + +Version: 1.0 | Ratified: 2025-12-08 | Last Amended: 2025-12-08 + +# **END OF CONSTITUTION (v1.0)** -**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE] - From ccb4784919dcf64d39cbfeada1dcb3e2b5cdbe01 Mon Sep 17 00:00:00 2001 From: Steven French Date: Tue, 9 Dec 2025 10:19:47 -0500 Subject: [PATCH 10/13] add feature spec --- high_level_features.md | 235 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 high_level_features.md diff --git a/high_level_features.md b/high_level_features.md new file mode 100644 index 0000000..1cad472 --- /dev/null +++ b/high_level_features.md @@ -0,0 +1,235 @@ +# High-Level Features - Yugastore E-Commerce Platform + +This document outlines the major features and subfeatures of the Yugastore application, derived from analysis of the React frontend components, API controllers, and backend microservices. + +--- + +## 1. Product Catalog + +The core product browsing and discovery feature of the platform. + +### 1.1 Product Listing +- Browse all products with pagination (12 items per page) +- Navigate between pages (Previous/Next) +- View product thumbnails, titles, prices, and star ratings +- Quick "Add to Cart" button on each product card + +### 1.2 Category Navigation +Primary categories displayed in the navigation bar: +- Books +- Music +- Beauty +- Electronics + +Extended categories available in footer: +- Kitchen & Dining +- Toys & Games +- Pet Supplies +- Grocery & Gourmet Food +- Video Games +- Movies & TV +- Arts, Crafts & Sewing +- Home & Kitchen +- Patio, Lawn & Garden +- Health & Personal Care +- Cell Phones & Accessories +- Industrial & Scientific +- Sports & Outdoors + +### 1.3 Product Details Page +- Full product image display +- Product title and description +- Price display +- Star rating visualization (5-star system with half-stars) +- Number of reviews and total stars +- Brand information +- "Add to Cart" button + +### 1.4 Product Recommendations +- "Also Bought" section showing related products +- Based on `also_bought`, `also_viewed`, `bought_together`, and `buy_after_viewing` data + +### 1.5 Product Sorting +Available sort options: +- By highest rating (`num_stars`) +- By most reviews (`num_reviews`) +- By best selling (`num_buys`) +- By most pageviews (`num_views`) + +--- + +## 2. Shopping Cart + +Persistent shopping cart functionality across the user session. + +### 2.1 Cart Management +- Add items to cart from product listings or detail pages +- Remove items from cart +- View cart contents with product images and details +- Real-time cart count display in navigation bar +- Visual feedback on cart errors + +### 2.2 Cart Display +- Product image, title, and link to product page +- Individual product price +- Quantity of each item +- Running subtotal calculation +- Tax display (currently $0.00) + +### 2.3 Cart Persistence +- Cart data persisted per user session +- Cart automatically fetched on page load +- Cart state maintained during navigation + +--- + +## 3. Checkout & Orders + +Complete order processing workflow. + +### 3.1 Checkout Process +- Single-click checkout from cart page +- Inventory validation before purchase +- Out-of-stock detection with appropriate messaging +- Transactional order creation (using Cassandra transactions) + +### 3.2 Order Confirmation +- Order number generation (UUID-based) +- Order details summary (products, quantities, total) +- "Thank you" confirmation message +- Order number displayed as `#kmp-{orderNumber}` + +### 3.3 Inventory Management +- Real-time inventory quantity tracking +- Automatic inventory deduction on successful checkout +- Validation against available stock + +### 3.4 Order Records +- Order ID +- User ID association +- Order details (items purchased) +- Order timestamp +- Order total amount + +--- + +## 4. User Authentication + +User registration and login system (via login-microservice). + +### 4.1 User Registration +- Registration form with validation +- Username and password fields +- Password confirmation +- Redirect to login after successful registration + +### 4.2 User Login +- Username/password authentication +- Error messaging for invalid credentials +- Logout functionality with confirmation message +- Session-based authentication + +### 4.3 User Management +- User role support (role-based access) +- User validation (via UserValidator) +- Secure password handling + +--- + +## 5. Homepage & Marketing + +Landing page experience with promotional content. + +### 5.1 Hero Section +- Full-width promotional banner/image +- Brand showcase area + +### 5.2 Bestseller Highlights +- Featured products from each major category: + - Bestsellers in Books (4 items) + - Bestsellers in Music (4 items) + - Bestsellers in Beauty (4 items) + - Bestsellers in Electronics (4 items) +- Quick links to category pages + +### 5.3 Newsletter Subscription +- Email subscription form +- Marketing messaging ("Let's keep the conversation going") +- Call-to-action for newsletter signup + +--- + +## 6. Navigation & UI + +Site-wide navigation and user interface elements. + +### 6.1 Navigation Bar +- Logo with link to homepage +- Category links with icons (Books, Music, Beauty, Electronics) +- Shopping cart icon with item count badge +- Scroll-responsive styling (transparent to solid) +- Active state highlighting for current category + +### 6.2 Footer +- Logo display +- Brand attribution (YugaByte DB) +- Copyright notice +- Extended category links (18 categories) +- External link to yugabyte.com + +### 6.3 Responsive Design +- Mobile-friendly layouts (Bootstrap grid) +- Adaptive navigation +- Responsive product grids (1-4 columns based on viewport) + +--- + +## 7. Microservices Architecture + +Backend services supporting the features above. + +| Service | Port | Responsibility | +|---------|------|----------------| +| Eureka Server | 8761 | Service discovery and registration | +| API Gateway | 8081 | Request routing and API aggregation | +| Products | 8082 | Product catalog and metadata | +| Cart | 8083 | Shopping cart operations | +| Login | 8085 | User authentication | +| Checkout | 8086 | Order processing and inventory | +| React UI | 8080 | Frontend web application | + +--- + +## 8. Data Storage + +Database layer powered by YugabyteDB. + +### 8.1 YCQL (Cassandra-compatible) +- Products table (metadata, images, pricing) +- Product inventory tracking +- Product rankings by category +- Orders table +- Shopping cart storage + +### 8.2 YSQL (PostgreSQL-compatible) +- User authentication data +- Role-based permissions + +--- + +## Feature Summary Matrix + +| Feature Area | Implemented | Partially Implemented | Not Implemented | +|--------------|-------------|----------------------|-----------------| +| Product Browsing | Yes | - | - | +| Category Filtering | Yes | - | - | +| Product Search | - | - | No search bar | +| Shopping Cart | Yes | - | - | +| Checkout | Yes | - | - | +| User Auth | Yes | - | - | +| Product Reviews | Display only | Write reviews | - | +| Wishlists | - | - | No | +| Order History | - | - | No user-facing view | +| Product Recommendations | Yes | - | - | +| Newsletter | UI only | Backend integration | - | + From 306f3b93094f0b27bf4019e13848ef5d653fbdfc Mon Sep 17 00:00:00 2001 From: Steven French Date: Tue, 9 Dec 2025 10:30:34 -0500 Subject: [PATCH 11/13] moving mermaid arch and high level features spec to its own folder. --- architecture.mmd => generated_docs/architecture.mmd | 0 high_level_features.md => generated_docs/high_level_features.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename architecture.mmd => generated_docs/architecture.mmd (100%) rename high_level_features.md => generated_docs/high_level_features.md (100%) diff --git a/architecture.mmd b/generated_docs/architecture.mmd similarity index 100% rename from architecture.mmd rename to generated_docs/architecture.mmd diff --git a/high_level_features.md b/generated_docs/high_level_features.md similarity index 100% rename from high_level_features.md rename to generated_docs/high_level_features.md From ee11d72dd1ab619ec2348900d1b61d99e3c9d9e4 Mon Sep 17 00:00:00 2001 From: ironchef001 Date: Tue, 9 Dec 2025 11:55:46 -0500 Subject: [PATCH 12/13] Refactor constitution: Extract business requirements - Created docs/inputs/business-requirements.md with functional and non-functional requirements - Removed Purpose/Scope, Functional Requirements, and Non-Functional Requirements sections from constitution - Renumbered remaining sections for consistency - Constitution now focuses on engineering principles, architecture, and governance --- .specify/memory/constitution.md | 158 ++++++--------------------- docs/inputs/business-requirements.md | 96 ++++++++++++++++ 2 files changed, 130 insertions(+), 124 deletions(-) create mode 100644 docs/inputs/business-requirements.md diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index 18fc16b..90ddac3 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -4,40 +4,21 @@ --- -## **1. Purpose and Scope** +## **1. Core Values & Architectural Principles** -This constitution establishes the **foundational principles, design philosophy, engineering rules, governance, and SDLC workflow** for building and operating a **cloud-native, scalable, resilient microservices-based e-commerce platform**. -The system supports: - -* Online bookstore shopping experience -* Product catalog and search -* Cart and checkout -* Order lifecycle & fulfillment -* Identity, authentication & authorization -* Payment routing (future/optional) -* Inventory and data storage -* API gateway & routing -* Observability, resilience, performance - -This constitution is **technology-agnostic**, forward-looking, and supports **AI-assisted, spec-driven development workflows**, ensuring durable engineering discipline and predictable delivery. - ---- - -## **2. Core Values & Architectural Principles** - -### **2.1 Cloud-Native First** +### **1.1 Cloud-Native First** * All components must be designed for **horizontal scalability**, **stateless compute**, and **managed cloud services**. * Prefer serverless or containerized workloads over self-managed infrastructure. * Minimize operational burden by adopting managed services (databases, queues, identity providers). -### **2.2 Microservices with Clear Boundaries** +### **1.2 Microservices with Clear Boundaries** * Each service owns a **single, cohesive domain** (e.g., Catalog, Cart, Checkout, Authentication). * Services communicate through **well-defined APIs**; avoid implicit dependencies. * No shared mutable database across services. -### **2.3 API Contract Stability** +### **1.3 API Contract Stability** * RESTful APIs must have: @@ -46,24 +27,24 @@ This constitution is **technology-agnostic**, forward-looking, and supports **AI * Strong schema validation * Breaking changes require proper version rollouts. -### **2.4 Fail-Fast, Safe-to-Fail** +### **1.4 Fail-Fast, Safe-to-Fail** * Services should detect invalid states early and fail gracefully. * Degradation modes must protect user experience (e.g., cached catalog if upstream fails). -### **2.5 Test-First, Test-Always** +### **1.5 Test-First, Test-Always** * No code is merged without automated tests. * Unit, integration, contract, and load tests are required. * APIs require machine-readable contracts enabling automated tests. -### **2.6 Security & Privacy by Default** +### **1.6 Security & Privacy by Default** * Zero-trust architecture. * Enforce authentication, authorization, input validation, and output encoding. * No PII or sensitive customer data stored without encryption in transit and at rest. -### **2.7 Observability as a First-Class Citizen** +### **1.7 Observability as a First-Class Citizen** * Every service emits: @@ -74,80 +55,9 @@ This constitution is **technology-agnostic**, forward-looking, and supports **AI --- -## **3. Functional Requirements (High-Level)** - -### **3.1 Product Catalog** - -* Browse/search books -* Detail pages with pricing, metadata, availability -* Support category filtering & pagination - -### **3.2 Shopping Cart** - -* Add/remove/update items -* Persist carts for authenticated users -* Stateless operations backed by durable storage - -### **3.3 Checkout & Order Processing** - -* Tax/shipping calculation -* Order summary -* Payment flow (future) -* Order creation & order tracking lifecycle - -### **3.4 User Authentication** - -* Support login/signup -* Token-based session management -* Integrate with cloud identity provider (Cognito/Okta/etc.) - -### **3.5 System Management APIs** - -* Health checks -* Service discovery -* Operational endpoints - ---- - -## **4. Non-Functional Requirements** - -### **4.1 Performance** - -* API response time target: ≤ 200 ms at P95 -* Catalog & cart endpoints must support burst traffic - -### **4.2 Scalability** - -* Horizontal scaling for compute and storage -* Services must avoid single points of failure - -### **4.3 Reliability** - -* SLO: 99.9% uptime -* Redundancy for critical services -* Graceful degradation & fallback strategies - -### **4.4 Resilience** - -* Circuit breakers, retries, backoff strategies -* Rate limiting and API quota enforcement - -### **4.5 Accessibility (for UI)** - -* WCAG AA compliance -* Keyboard navigation support -* Alternative text for images - -### **4.6 Internationalization (future)** - -* Multi-language content -* Configurable currency and locale formats - ---- - -## **5. System Architecture Principles** +## **2. System Architecture Principles** -### **5.1 Front-End (React or modern framework)** +### **2.1 Front-End (React or modern framework)** * Modular components * API-driven UI @@ -155,26 +65,26 @@ This constitution is **technology-agnostic**, forward-looking, and supports **AI * Strong and consistent UX patterns * Do not embed business logic in the UI layer -### **5.2 API Gateway** +### **2.2 API Gateway** * Single entry point for all API consumers * Handles routing, rate limiting, request validation * Should enforce authentication before forwarding requests -### **5.3 Service Discovery** +### **2.3 Service Discovery** * Each service must register itself * Health checks determine routing eligibility * Prefer managed service discovery (cloud-native) -### **5.4 Backend Microservices** +### **2.4 Backend Microservices** * Stateless compute * Clear ownership of business capability * Storage abstraction: repository/persistence layer * Domain models with explicit boundaries -### **5.5 Data Storage** +### **2.5 Data Storage** * Based on domain needs: SQL or NoSQL * Services own their data @@ -183,9 +93,9 @@ This constitution is **technology-agnostic**, forward-looking, and supports **AI --- -## **6. SDLC & AI-Enabled Engineering Workflow** +## **3. SDLC & AI-Enabled Engineering Workflow** -### **6.1 Spec-Driven Development** +### **3.1 Spec-Driven Development** All code development begins with: @@ -196,7 +106,7 @@ All code development begins with: No coding starts without an approved spec. -### **6.2 AI-Assisted Engineering (LLM + Spec-Driven)** +### **3.2 AI-Assisted Engineering (LLM + Spec-Driven)** LLM participation includes: @@ -209,13 +119,13 @@ LLM participation includes: Developers must review all generated outputs before merging. -### **6.3 Governance Requirements** +### **3.3 Governance Requirements** * Architecture decisions logged as ADRs * No undocumented design deviations * Constitution must guide every service and artifact -### **6.4 Branching & Versioning** +### **3.4 Branching & Versioning** ``` feature/US001-short-description @@ -227,7 +137,7 @@ feature/US002-short-description * Minor → backward-compatible enhancements * Major → breaking changes -### **6.5 CI/CD Requirements** +### **3.5 CI/CD Requirements** * Automated lint, test, security, dependency scanning * Branch Protection rules enforced @@ -235,7 +145,7 @@ feature/US002-short-description --- -## **7. Quality Gates** +## **4. Quality Gates** Every merge request must include: @@ -249,15 +159,15 @@ No exceptions. --- -## **8. Observability & Operations** +## **5. Observability & Operations** -### **8.1 Required Telemetry** +### **5.1 Required Telemetry** * Logs → structured JSON * Metrics → latency, error rate, throughput * Traces → service-to-service spans -### **8.2 Error Handling & Monitoring** +### **5.2 Error Handling & Monitoring** * Centralized logging * Alerts for SLO violations @@ -265,7 +175,7 @@ No exceptions. --- -## **9. Security & Compliance** +## **6. Security & Compliance** * Validate all inputs * Sanitize outputs @@ -281,42 +191,42 @@ Data Protection: --- -## **10. Documentation Framework** +## **7. Documentation Framework** -### **10.1 docs/inputs/** +### **7.1 docs/inputs/** Raw business requirements, diagrams, meeting notes. -### **10.2 docs/specs/** +### **7.2 docs/specs/** AI-refined specifications (single source of truth). -### **10.3 docs/plan/** +### **7.3 docs/plan/** High-level delivery strategy. -### **10.4 docs/tasks/** +### **7.4 docs/tasks/** User stories + task breakdown for implementation. -### **10.5 docs/adr/** +### **7.5 docs/adr/** Architectural decisions and rationale. -### **10.6 constitution.md** +### **7.6 constitution.md** This file—governing everything. --- -## **11. Compliance with This Constitution** +## **8. Compliance with This Constitution** All contributors—human and AI—must adhere to the constitution. Deviations must be explicitly approved through an ADR process. --- -## **12. Future Evolution** +## **9. Future Evolution** This constitution will evolve as: diff --git a/docs/inputs/business-requirements.md b/docs/inputs/business-requirements.md new file mode 100644 index 0000000..d5c7ddc --- /dev/null +++ b/docs/inputs/business-requirements.md @@ -0,0 +1,96 @@ +# **Business Requirements for Cloud-Native E-Commerce Bookstore Platform** + +--- + +## **1. Purpose and Scope** + +This document establishes the **business requirements, functional capabilities, and non-functional quality attributes** for building and operating a **cloud-native, scalable, resilient microservices-based e-commerce platform**. + +The system supports: + +* Online bookstore shopping experience +* Product catalog and search +* Cart and checkout +* Order lifecycle & fulfillment +* Identity, authentication & authorization +* Payment routing (future/optional) +* Inventory and data storage +* API gateway & routing +* Observability, resilience, performance + +This is a **technology-agnostic**, forward-looking platform designed to support **AI-assisted, spec-driven development workflows**, ensuring durable engineering discipline and predictable delivery. + +--- + +## **2. Functional Requirements (High-Level)** + +### **2.1 Product Catalog** + +* Browse/search books +* Detail pages with pricing, metadata, availability +* Support category filtering & pagination + +### **2.2 Shopping Cart** + +* Add/remove/update items +* Persist carts for authenticated users +* Stateless operations backed by durable storage + +### **2.3 Checkout & Order Processing** + +* Tax/shipping calculation +* Order summary +* Payment flow (future) +* Order creation & order tracking lifecycle + +### **2.4 User Authentication** + +* Support login/signup +* Token-based session management +* Integrate with cloud identity provider (Cognito/Okta/etc.) + +### **2.5 System Management APIs** + +* Health checks +* Service discovery +* Operational endpoints + +--- + +## **3. Non-Functional Requirements** + +### **3.1 Performance** + +* API response time target: ≤ 200 ms at P95 +* Catalog & cart endpoints must support burst traffic + +### **3.2 Scalability** + +* Horizontal scaling for compute and storage +* Services must avoid single points of failure + +### **3.3 Reliability** + +* SLO: 99.9% uptime +* Redundancy for critical services +* Graceful degradation & fallback strategies + +### **3.4 Resilience** + +* Circuit breakers, retries, backoff strategies +* Rate limiting and API quota enforcement + +### **3.5 Accessibility (for UI)** + +* WCAG AA compliance +* Keyboard navigation support +* Alternative text for images + +### **3.6 Internationalization (future)** + +* Multi-language content +* Configurable currency and locale formats + +--- + +**Last Updated:** 2025-12-09 From 4c928a7ea4ac11c7fe03d9c617356fc90c5e0ec8 Mon Sep 17 00:00:00 2001 From: Steven French Date: Tue, 9 Dec 2025 12:14:38 -0500 Subject: [PATCH 13/13] moving files. --- {generated_docs => docs/inputs}/architecture.mmd | 0 {generated_docs => docs/inputs}/high_level_features.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {generated_docs => docs/inputs}/architecture.mmd (100%) rename {generated_docs => docs/inputs}/high_level_features.md (100%) diff --git a/generated_docs/architecture.mmd b/docs/inputs/architecture.mmd similarity index 100% rename from generated_docs/architecture.mmd rename to docs/inputs/architecture.mmd diff --git a/generated_docs/high_level_features.md b/docs/inputs/high_level_features.md similarity index 100% rename from generated_docs/high_level_features.md rename to docs/inputs/high_level_features.md