285 lines
10 KiB
Bash
285 lines
10 KiB
Bash
#!/bin/bash
|
|
# Integration tests for Workflow 2 — Estimate Validation
|
|
# Tests the full cycle: DRAFT→SUBMITTED→APPROVED→LOCKED
|
|
# Plus: revision flow, versioning, RBAC
|
|
|
|
BASE="http://localhost:3001/api/v1"
|
|
PASS=0
|
|
FAIL=0
|
|
TOTAL=0
|
|
|
|
# Helper: login and get token
|
|
login() {
|
|
local email=$1
|
|
local resp
|
|
resp=$(python3 -c "
|
|
import urllib.request, json
|
|
data = json.dumps({'email':'$email','password':'MetallKart2026!'}).encode()
|
|
req = urllib.request.Request('$BASE/auth/login', data, {'Content-Type':'application/json'})
|
|
resp = urllib.request.urlopen(req)
|
|
print(json.loads(resp.read())['accessToken'])
|
|
" 2>/dev/null)
|
|
echo "$resp"
|
|
}
|
|
|
|
# Helper: make API call
|
|
api() {
|
|
local method=$1
|
|
local path=$2
|
|
local token=$3
|
|
local body=$4
|
|
python3 -c "
|
|
import urllib.request, json, sys
|
|
url = '$BASE$path'
|
|
headers = {'Authorization': 'Bearer $token'}
|
|
data = None
|
|
if '$body':
|
|
data = '$body'.encode()
|
|
headers['Content-Type'] = 'application/json'
|
|
req = urllib.request.Request(url, data, headers, method='$method')
|
|
try:
|
|
resp = urllib.request.urlopen(req)
|
|
result = resp.read().decode()
|
|
print(json.dumps({'status': resp.status, 'body': json.loads(result)}))
|
|
except urllib.error.HTTPError as e:
|
|
result = e.read().decode()
|
|
try:
|
|
body = json.loads(result)
|
|
except:
|
|
body = result
|
|
print(json.dumps({'status': e.code, 'body': body}))
|
|
" 2>/dev/null
|
|
}
|
|
|
|
assert_status() {
|
|
local test_name=$1
|
|
local expected=$2
|
|
local response=$3
|
|
TOTAL=$((TOTAL+1))
|
|
local actual
|
|
actual=$(echo "$response" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['status'])" 2>/dev/null)
|
|
if [ "$actual" = "$expected" ]; then
|
|
echo " ✓ $test_name (HTTP $actual)"
|
|
PASS=$((PASS+1))
|
|
else
|
|
echo " ✗ $test_name — expected HTTP $expected, got $actual"
|
|
echo " Response: $(echo "$response" | head -c 300)"
|
|
FAIL=$((FAIL+1))
|
|
fi
|
|
}
|
|
|
|
extract() {
|
|
local response=$1
|
|
local field=$2
|
|
echo "$response" | python3 -c "
|
|
import json, sys
|
|
data = json.loads(sys.stdin.read())
|
|
parts = '$field'.split('.')
|
|
obj = data['body']
|
|
for p in parts:
|
|
if isinstance(obj, dict):
|
|
obj = obj.get(p)
|
|
else:
|
|
obj = None
|
|
break
|
|
print(obj if obj is not None else '')
|
|
" 2>/dev/null
|
|
}
|
|
|
|
echo "=== Workflow 2 — Estimate Validation Integration Tests ==="
|
|
echo ""
|
|
|
|
# Login as different users
|
|
echo "1. Authenticating users..."
|
|
TOKEN_ENGINEER=$(login "timofey@metallcart.ru")
|
|
TOKEN_DIRECTOR=$(login "maxim@metallcart.ru")
|
|
TOKEN_COMMERCIAL=$(login "anna.v@metallcart.ru")
|
|
TOKEN_ADMIN=$(login "louis@metallcart.ru")
|
|
|
|
if [ -z "$TOKEN_ENGINEER" ] || [ -z "$TOKEN_DIRECTOR" ]; then
|
|
echo " ✗ Failed to authenticate. Aborting."
|
|
exit 1
|
|
fi
|
|
echo " ✓ All users authenticated"
|
|
echo ""
|
|
|
|
# Get test order ID
|
|
echo "2. Fetching test order..."
|
|
RESP=$(api GET "/orders?page=1&limit=1" "$TOKEN_ENGINEER")
|
|
ORDER_ID=$(extract "$RESP" "items.0.id")
|
|
if [ -z "$ORDER_ID" ] || [ "$ORDER_ID" = "None" ]; then
|
|
# Use the seeded order directly
|
|
ORDER_ID=1
|
|
fi
|
|
echo " Using order ID: $ORDER_ID"
|
|
echo ""
|
|
|
|
# === Test: Create estimate (ENGINEER) ===
|
|
echo "3. Creating estimate..."
|
|
RESP=$(api POST "/estimates" "$TOKEN_ENGINEER" "{\"orderId\":$ORDER_ID,\"totalServices\":120000,\"totalMaterials\":380000,\"totalLabor\":90000,\"supplyOverhead\":25000,\"margin\":160000,\"tradeSurcharge\":10000,\"sellingPrice\":785000}")
|
|
assert_status "Engineer creates estimate" "201" "$RESP"
|
|
ESTIMATE_ID=$(extract "$RESP" "id")
|
|
EST_VERSION=$(extract "$RESP" "version")
|
|
echo " Created estimate ID=$ESTIMATE_ID version=$EST_VERSION"
|
|
echo ""
|
|
|
|
# === Test: RBAC — Commercial cannot create ===
|
|
echo "4. RBAC tests..."
|
|
RESP=$(api POST "/estimates" "$TOKEN_COMMERCIAL" "{\"orderId\":$ORDER_ID,\"totalServices\":1000,\"totalMaterials\":1000,\"totalLabor\":1000,\"supplyOverhead\":100,\"margin\":100,\"tradeSurcharge\":0,\"sellingPrice\":3200}")
|
|
assert_status "Commercial cannot create estimate (403)" "403" "$RESP"
|
|
|
|
# === Test: Commercial cannot submit ===
|
|
RESP=$(api POST "/estimates/$ESTIMATE_ID/submit" "$TOKEN_COMMERCIAL")
|
|
assert_status "Commercial cannot submit estimate (403)" "403" "$RESP"
|
|
echo ""
|
|
|
|
# === Test: Get estimate ===
|
|
echo "5. Read estimate..."
|
|
RESP=$(api GET "/estimates/$ESTIMATE_ID" "$TOKEN_ENGINEER")
|
|
assert_status "Get estimate by ID" "200" "$RESP"
|
|
EST_STATUS=$(extract "$RESP" "status")
|
|
echo " Status: $EST_STATUS"
|
|
echo ""
|
|
|
|
# === Test: Update estimate (DRAFT) ===
|
|
echo "6. Update estimate in DRAFT..."
|
|
RESP=$(api PUT "/estimates/$ESTIMATE_ID" "$TOKEN_ENGINEER" "{\"sellingPrice\":800000}")
|
|
assert_status "Engineer updates estimate" "200" "$RESP"
|
|
echo ""
|
|
|
|
# === Test: Submit estimate ===
|
|
echo "7. Submit estimate..."
|
|
RESP=$(api POST "/estimates/$ESTIMATE_ID/submit" "$TOKEN_ENGINEER")
|
|
assert_status "Engineer submits estimate" "200" "$RESP"
|
|
EST_STATUS=$(extract "$RESP" "status")
|
|
echo " Status after submit: $EST_STATUS"
|
|
echo ""
|
|
|
|
# === Test: Cannot edit after submit ===
|
|
echo "8. Cannot edit after submit..."
|
|
RESP=$(api PUT "/estimates/$ESTIMATE_ID" "$TOKEN_ENGINEER" "{\"sellingPrice\":900000}")
|
|
assert_status "Cannot edit SUBMITTED estimate (400)" "400" "$RESP"
|
|
echo ""
|
|
|
|
# === Test: Request revision (DIRECTOR) ===
|
|
echo "9. Director requests revision..."
|
|
RESP=$(api POST "/estimates/$ESTIMATE_ID/request-revision" "$TOKEN_DIRECTOR" "{\"comment\":\"Пересмотрите стоимость материалов — слишком высокая\"}")
|
|
assert_status "Director requests revision" "200" "$RESP"
|
|
EST_STATUS=$(extract "$RESP" "status")
|
|
echo " Status after revision request: $EST_STATUS"
|
|
echo ""
|
|
|
|
# === Test: Engineer edits after revision request ===
|
|
echo "10. Engineer edits after revision request..."
|
|
RESP=$(api PUT "/estimates/$ESTIMATE_ID" "$TOKEN_ENGINEER" "{\"totalMaterials\":350000,\"sellingPrice\":770000}")
|
|
assert_status "Engineer edits after revision request" "200" "$RESP"
|
|
EST_STATUS=$(extract "$RESP" "status")
|
|
echo " Status after edit (back to DRAFT): $EST_STATUS"
|
|
echo ""
|
|
|
|
# === Test: Re-submit ===
|
|
echo "11. Re-submit after revision..."
|
|
RESP=$(api POST "/estimates/$ESTIMATE_ID/submit" "$TOKEN_ENGINEER")
|
|
assert_status "Engineer re-submits" "200" "$RESP"
|
|
echo ""
|
|
|
|
# === Test: Approve and auto-lock ===
|
|
echo "12. Director approves (auto-lock)..."
|
|
RESP=$(api POST "/estimates/$ESTIMATE_ID/approve" "$TOKEN_DIRECTOR")
|
|
assert_status "Director approves estimate" "200" "$RESP"
|
|
EST_STATUS=$(extract "$RESP" "status")
|
|
IS_LOCKED=$(extract "$RESP" "isLocked")
|
|
echo " Status: $EST_STATUS, isLocked: $IS_LOCKED"
|
|
echo ""
|
|
|
|
# === Test: Cannot edit locked estimate ===
|
|
echo "13. Cannot edit locked estimate..."
|
|
RESP=$(api PUT "/estimates/$ESTIMATE_ID" "$TOKEN_ENGINEER" "{\"sellingPrice\":999999}")
|
|
assert_status "Cannot edit LOCKED estimate (400)" "400" "$RESP"
|
|
echo ""
|
|
|
|
# === Test: Cannot submit locked estimate ===
|
|
echo "14. Cannot submit locked estimate..."
|
|
RESP=$(api POST "/estimates/$ESTIMATE_ID/submit" "$TOKEN_ENGINEER")
|
|
assert_status "Cannot submit LOCKED estimate (400)" "400" "$RESP"
|
|
echo ""
|
|
|
|
# === Test: Version history ===
|
|
echo "15. Version history..."
|
|
RESP=$(api GET "/estimates/order/$ORDER_ID/versions" "$TOKEN_ENGINEER")
|
|
assert_status "Get version history" "200" "$RESP"
|
|
echo ""
|
|
|
|
# === Test: Create new version from locked ===
|
|
echo "16. Create new version..."
|
|
RESP=$(api POST "/estimates/order/$ORDER_ID/new-version" "$TOKEN_ENGINEER")
|
|
assert_status "Engineer creates new version" "201" "$RESP"
|
|
NEW_ESTIMATE_ID=$(extract "$RESP" "id")
|
|
NEW_VERSION=$(extract "$RESP" "version")
|
|
echo " New estimate ID=$NEW_ESTIMATE_ID version=$NEW_VERSION"
|
|
echo ""
|
|
|
|
# === Test: Cannot create another draft while one exists ===
|
|
echo "17. Cannot create duplicate draft..."
|
|
RESP=$(api POST "/estimates/order/$ORDER_ID/new-version" "$TOKEN_ENGINEER")
|
|
assert_status "Cannot create another draft (400)" "400" "$RESP"
|
|
echo ""
|
|
|
|
# === Test: List estimates with filters ===
|
|
echo "18. List estimates..."
|
|
RESP=$(api GET "/estimates?orderId=$ORDER_ID" "$TOKEN_ENGINEER")
|
|
assert_status "List estimates for order" "200" "$RESP"
|
|
TOTAL_ITEMS=$(extract "$RESP" "total")
|
|
echo " Total estimates for order: $TOTAL_ITEMS"
|
|
echo ""
|
|
|
|
# === Test: RBAC — Engineer cannot approve ===
|
|
echo "19. RBAC — Engineer cannot approve..."
|
|
# Submit the new version first
|
|
RESP=$(api PUT "/estimates/$NEW_ESTIMATE_ID" "$TOKEN_ENGINEER" "{\"totalServices\":130000,\"totalMaterials\":360000,\"totalLabor\":95000,\"supplyOverhead\":26000,\"margin\":165000,\"tradeSurcharge\":12000,\"sellingPrice\":788000}")
|
|
assert_status "Update new version" "200" "$RESP"
|
|
RESP=$(api POST "/estimates/$NEW_ESTIMATE_ID/submit" "$TOKEN_ENGINEER")
|
|
assert_status "Submit new version" "200" "$RESP"
|
|
RESP=$(api POST "/estimates/$NEW_ESTIMATE_ID/approve" "$TOKEN_ENGINEER")
|
|
assert_status "Engineer cannot approve (403)" "403" "$RESP"
|
|
echo ""
|
|
|
|
# === Test: Director approves new version ===
|
|
echo "20. Director approves new version..."
|
|
RESP=$(api POST "/estimates/$NEW_ESTIMATE_ID/approve" "$TOKEN_DIRECTOR")
|
|
assert_status "Director approves v2" "200" "$RESP"
|
|
EST_STATUS=$(extract "$RESP" "status")
|
|
echo " Status: $EST_STATUS"
|
|
echo ""
|
|
|
|
# === Test: Revision request requires comment ===
|
|
echo "21. Revision request requires comment..."
|
|
# Create a third version to test
|
|
RESP=$(api POST "/estimates/order/$ORDER_ID/new-version" "$TOKEN_ENGINEER")
|
|
V3_ID=$(extract "$RESP" "id")
|
|
RESP=$(api PUT "/estimates/$V3_ID" "$TOKEN_ENGINEER" "{\"totalServices\":135000,\"totalMaterials\":365000,\"totalLabor\":98000,\"supplyOverhead\":27000,\"margin\":170000,\"tradeSurcharge\":13000,\"sellingPrice\":808000}")
|
|
RESP=$(api POST "/estimates/$V3_ID/submit" "$TOKEN_ENGINEER")
|
|
RESP=$(api POST "/estimates/$V3_ID/request-revision" "$TOKEN_DIRECTOR" "{}")
|
|
assert_status "Revision without comment fails (400)" "400" "$RESP"
|
|
echo ""
|
|
|
|
# === Test: Admin can do everything ===
|
|
echo "22. Admin powers..."
|
|
RESP=$(api POST "/estimates/$V3_ID/approve" "$TOKEN_ADMIN")
|
|
assert_status "Admin can approve" "200" "$RESP"
|
|
echo ""
|
|
|
|
# === Final version history ===
|
|
echo "23. Final version history..."
|
|
RESP=$(api GET "/estimates/order/$ORDER_ID/versions" "$TOKEN_ENGINEER")
|
|
assert_status "Final version history" "200" "$RESP"
|
|
echo ""
|
|
|
|
echo "========================================="
|
|
echo "Results: $PASS passed, $FAIL failed (out of $TOTAL tests)"
|
|
echo "========================================="
|
|
|
|
if [ "$FAIL" -gt 0 ]; then
|
|
exit 1
|
|
fi
|