From bf0352efd56338c9b7aee2c01a532baf795b0388 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 05:05:50 +0000 Subject: [PATCH 1/2] =?UTF-8?q?feat(product):=20T5=20=E2=80=94=20E2E=20smo?= =?UTF-8?q?ke=20test=20(health,=20auth,=20CRUD=20through=20gateway)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Product/tests/docker-compose.test.yml | 40 +++++++ src/Services/Product/tests/smoke-test.sh | 107 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 src/Services/Product/tests/docker-compose.test.yml create mode 100755 src/Services/Product/tests/smoke-test.sh diff --git a/src/Services/Product/tests/docker-compose.test.yml b/src/Services/Product/tests/docker-compose.test.yml new file mode 100644 index 0000000..71aa813 --- /dev/null +++ b/src/Services/Product/tests/docker-compose.test.yml @@ -0,0 +1,40 @@ +version: '3.8' + +services: + productdb: + image: postgres:16-alpine + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: productdb + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 2s + timeout: 5s + retries: 10 + + product-service: + build: + context: ../../../ + dockerfile: Services/Product/Product.API/Dockerfile + ports: + - "5004:5004" + depends_on: + productdb: + condition: service_healthy + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ConnectionStrings__DefaultConnection=Host=productdb;Database=productdb;Username=postgres;Password=postgres + + api-gateway: + build: + context: ../../../ + dockerfile: ApiGateway/Dockerfile + ports: + - "5000:5000" + depends_on: + - product-service + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ReverseProxy__Clusters__product-cluster__Destinations__destination1__Address=http://product-service:5004/ diff --git a/src/Services/Product/tests/smoke-test.sh b/src/Services/Product/tests/smoke-test.sh new file mode 100755 index 0000000..5ad6e02 --- /dev/null +++ b/src/Services/Product/tests/smoke-test.sh @@ -0,0 +1,107 @@ +#!/bin/bash +set -e + +GATEWAY_URL="${GATEWAY_URL:-http://localhost:5000}" +SERVICE_URL="${SERVICE_URL:-http://localhost:5004}" +JWT_SECRET="QuickApp_Microservices_SuperSecret_Key_For_Dev_Only_Min_32_Chars!" +PASS=0 +FAIL=0 + +# Generate a JWT token +generate_jwt() { + local header='{"alg":"HS256","typ":"JWT"}' + local payload="{\"sub\":\"test-user\",\"iss\":\"quickapp-identity\",\"aud\":\"quickapp-api\",\"exp\":$(($(date +%s) + 3600))}" + + local header_b64=$(echo -n "$header" | base64 -w0 | tr '+/' '-_' | tr -d '=') + local payload_b64=$(echo -n "$payload" | base64 -w0 | tr '+/' '-_' | tr -d '=') + local signature=$(echo -n "${header_b64}.${payload_b64}" | openssl dgst -sha256 -hmac "$JWT_SECRET" -binary | base64 -w0 | tr '+/' '-_' | tr -d '=') + + echo "${header_b64}.${payload_b64}.${signature}" +} + +assert_status() { + local description="$1" + local expected="$2" + local actual="$3" + + if [ "$actual" = "$expected" ]; then + echo "PASS: $description (HTTP $actual)" + PASS=$((PASS + 1)) + else + echo "FAIL: $description — expected $expected, got $actual" + FAIL=$((FAIL + 1)) + fi +} + +TOKEN=$(generate_jwt) +echo "Generated JWT token" +echo "Testing against gateway: $GATEWAY_URL" +echo "Testing against service: $SERVICE_URL" +echo "---" + +# Test 1: Health check (direct to service — public) +STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$SERVICE_URL/healthz") +assert_status "GET /healthz (direct) → 200" "200" "$STATUS" + +# Test 2: GET /api/product without token → 401 (direct) +STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$SERVICE_URL/api/product") +assert_status "GET /api/product without token (direct) → 401" "401" "$STATUS" + +# Test 3: GET /api/products without token → 401 (through gateway) +STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$GATEWAY_URL/api/products") +assert_status "GET /api/products without token (gateway) → 401" "401" "$STATUS" + +# Test 4: Create a category with valid JWT → 201 +RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$GATEWAY_URL/api/products/categories" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"Electronics","description":"Electronic devices","icon":"electronics-icon"}') +STATUS=$(echo "$RESPONSE" | tail -1) +BODY=$(echo "$RESPONSE" | sed '$d') +assert_status "POST /api/products/categories with JWT → 201" "201" "$STATUS" + +# Extract category ID +CATEGORY_ID=$(echo "$BODY" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) +echo "Created category ID: $CATEGORY_ID" + +# Test 5: Create a product with valid JWT → 201 +RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$GATEWAY_URL/api/products" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"Test Product\",\"description\":\"A test product\",\"icon\":\"test-icon\",\"buyingPrice\":10.50,\"sellingPrice\":15.99,\"unitsInStock\":100,\"isActive\":true,\"isDiscontinued\":false,\"parentId\":null,\"productCategoryId\":$CATEGORY_ID}") +STATUS=$(echo "$RESPONSE" | tail -1) +BODY=$(echo "$RESPONSE" | sed '$d') +assert_status "POST /api/products with JWT → 201" "201" "$STATUS" + +# Extract product ID +PRODUCT_ID=$(echo "$BODY" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) +echo "Created product ID: $PRODUCT_ID" + +# Test 6: GET /api/products with valid JWT → 200 (list includes product) +RESPONSE=$(curl -s -w "\n%{http_code}" "$GATEWAY_URL/api/products" \ + -H "Authorization: Bearer $TOKEN") +STATUS=$(echo "$RESPONSE" | tail -1) +BODY=$(echo "$RESPONSE" | sed '$d') +assert_status "GET /api/products with JWT → 200" "200" "$STATUS" + +# Verify the product is in the list +if echo "$BODY" | grep -q "Test Product"; then + echo "PASS: Response contains 'Test Product'" + PASS=$((PASS + 1)) +else + echo "FAIL: Response does not contain 'Test Product'" + FAIL=$((FAIL + 1)) +fi + +# Test 7: GET /api/products/{id} with valid JWT → 200 +STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$GATEWAY_URL/api/products/$PRODUCT_ID" \ + -H "Authorization: Bearer $TOKEN") +assert_status "GET /api/products/$PRODUCT_ID with JWT → 200" "200" "$STATUS" + +echo "---" +echo "Results: $PASS passed, $FAIL failed" + +if [ $FAIL -gt 0 ]; then + exit 1 +fi +echo "All smoke tests passed!" From dd6b5455cd1f3d8e0ede2f8d4c657aaeeeb6b8fe Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 05:16:00 +0000 Subject: [PATCH 2/2] fix(product): harden smoke test with readiness wait, empty-ID guards, and service healthcheck --- .../Product/tests/docker-compose.test.yml | 8 ++- src/Services/Product/tests/smoke-test.sh | 61 ++++++++++++++----- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/Services/Product/tests/docker-compose.test.yml b/src/Services/Product/tests/docker-compose.test.yml index 71aa813..a18604c 100644 --- a/src/Services/Product/tests/docker-compose.test.yml +++ b/src/Services/Product/tests/docker-compose.test.yml @@ -26,6 +26,11 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Development - ConnectionStrings__DefaultConnection=Host=productdb;Database=productdb;Username=postgres;Password=postgres + healthcheck: + test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/5004' || exit 1"] + interval: 2s + timeout: 5s + retries: 15 api-gateway: build: @@ -34,7 +39,8 @@ services: ports: - "5000:5000" depends_on: - - product-service + product-service: + condition: service_healthy environment: - ASPNETCORE_ENVIRONMENT=Development - ReverseProxy__Clusters__product-cluster__Destinations__destination1__Address=http://product-service:5004/ diff --git a/src/Services/Product/tests/smoke-test.sh b/src/Services/Product/tests/smoke-test.sh index 5ad6e02..c20c538 100755 --- a/src/Services/Product/tests/smoke-test.sh +++ b/src/Services/Product/tests/smoke-test.sh @@ -33,6 +33,28 @@ assert_status() { fi } +# Wait for services to be ready +wait_for_service() { + local url="$1" + local name="$2" + local retries=30 + local i=0 + echo "Waiting for $name at $url ..." + while [ $i -lt $retries ]; do + if curl -sf -o /dev/null "$url"; then + echo "$name is ready" + return 0 + fi + i=$((i + 1)) + sleep 1 + done + echo "ERROR: $name not ready after ${retries}s" + return 1 +} + +wait_for_service "$SERVICE_URL/healthz" "product-service" +wait_for_service "$GATEWAY_URL/healthz" "api-gateway" + TOKEN=$(generate_jwt) echo "Generated JWT token" echo "Testing against gateway: $GATEWAY_URL" @@ -65,17 +87,23 @@ CATEGORY_ID=$(echo "$BODY" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) echo "Created category ID: $CATEGORY_ID" # Test 5: Create a product with valid JWT → 201 -RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$GATEWAY_URL/api/products" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"name\":\"Test Product\",\"description\":\"A test product\",\"icon\":\"test-icon\",\"buyingPrice\":10.50,\"sellingPrice\":15.99,\"unitsInStock\":100,\"isActive\":true,\"isDiscontinued\":false,\"parentId\":null,\"productCategoryId\":$CATEGORY_ID}") -STATUS=$(echo "$RESPONSE" | tail -1) -BODY=$(echo "$RESPONSE" | sed '$d') -assert_status "POST /api/products with JWT → 201" "201" "$STATUS" - -# Extract product ID -PRODUCT_ID=$(echo "$BODY" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) -echo "Created product ID: $PRODUCT_ID" +if [ -z "$CATEGORY_ID" ]; then + echo "FAIL: POST /api/products — skipped, no category ID from previous step" + FAIL=$((FAIL + 1)) + PRODUCT_ID="" +else + RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$GATEWAY_URL/api/products" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"Test Product\",\"description\":\"A test product\",\"icon\":\"test-icon\",\"buyingPrice\":10.50,\"sellingPrice\":15.99,\"unitsInStock\":100,\"isActive\":true,\"isDiscontinued\":false,\"parentId\":null,\"productCategoryId\":$CATEGORY_ID}") + STATUS=$(echo "$RESPONSE" | tail -1) + BODY=$(echo "$RESPONSE" | sed '$d') + assert_status "POST /api/products with JWT → 201" "201" "$STATUS" + + # Extract product ID + PRODUCT_ID=$(echo "$BODY" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) + echo "Created product ID: $PRODUCT_ID" +fi # Test 6: GET /api/products with valid JWT → 200 (list includes product) RESPONSE=$(curl -s -w "\n%{http_code}" "$GATEWAY_URL/api/products" \ @@ -94,9 +122,14 @@ else fi # Test 7: GET /api/products/{id} with valid JWT → 200 -STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$GATEWAY_URL/api/products/$PRODUCT_ID" \ - -H "Authorization: Bearer $TOKEN") -assert_status "GET /api/products/$PRODUCT_ID with JWT → 200" "200" "$STATUS" +if [ -z "$PRODUCT_ID" ]; then + echo "FAIL: GET /api/products/{id} — skipped, no product ID from previous step" + FAIL=$((FAIL + 1)) +else + STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$GATEWAY_URL/api/products/$PRODUCT_ID" \ + -H "Authorization: Bearer $TOKEN") + assert_status "GET /api/products/$PRODUCT_ID with JWT → 200" "200" "$STATUS" +fi echo "---" echo "Results: $PASS passed, $FAIL failed"