4runr-os 2.9.32 → 2.9.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/apps/gateway/.dockerignore +11 -0
  2. package/apps/gateway/.eslintrc.json +28 -0
  3. package/apps/gateway/Dockerfile +122 -0
  4. package/apps/gateway/Dockerfile.bak +41 -0
  5. package/apps/gateway/create-test-script.sh +386 -0
  6. package/apps/gateway/debug-api-responses.sh +94 -0
  7. package/apps/gateway/debug-failing-tests-v2.sh +81 -0
  8. package/apps/gateway/debug-failing-tests.sh +89 -0
  9. package/apps/gateway/debug-responses.sh +83 -0
  10. package/apps/gateway/debug-test-responses.sh +54 -0
  11. package/apps/gateway/debug-tests.sh +119 -0
  12. package/apps/gateway/diagnose-test-failure.sh +53 -0
  13. package/apps/gateway/find-gateway.sh +37 -0
  14. package/apps/gateway/jest.config.cjs +50 -0
  15. package/apps/gateway/load-tests/k6-basic.js +128 -0
  16. package/apps/gateway/load-tests/k6-rate-limit.js +57 -0
  17. package/apps/gateway/minimal-test.sh +60 -0
  18. package/apps/gateway/package.json +66 -0
  19. package/apps/gateway/public/sentinel-dashboard.html +428 -0
  20. package/apps/gateway/quick-debug.sh +70 -0
  21. package/apps/gateway/scripts/seed-api-key.ts +63 -0
  22. package/apps/gateway/scripts/setup-test-env.sh +67 -0
  23. package/apps/gateway/simple-test.sh +72 -0
  24. package/apps/gateway/src/__tests__/auth.test.ts +272 -0
  25. package/apps/gateway/src/__tests__/devkit-api.test.ts +268 -0
  26. package/apps/gateway/src/__tests__/integration/authentication.test.ts +155 -0
  27. package/apps/gateway/src/__tests__/integration/e2e-comprehensive-fixed.test.ts +368 -0
  28. package/apps/gateway/src/__tests__/integration/e2e-workflow.test.ts +239 -0
  29. package/apps/gateway/src/__tests__/integration/helpers/test-server.ts +142 -0
  30. package/apps/gateway/src/__tests__/integration/idempotency.test.ts +213 -0
  31. package/apps/gateway/src/__tests__/integration/postgres-persistence.test.ts +173 -0
  32. package/apps/gateway/src/__tests__/integration/rate-limiting.test.ts +148 -0
  33. package/apps/gateway/src/__tests__/integration/sentinel.test.ts +152 -0
  34. package/apps/gateway/src/__tests__/no-persistence-mode.test.ts +180 -0
  35. package/apps/gateway/src/__tests__/rateLimit.test.ts +107 -0
  36. package/apps/gateway/src/__tests__/validation.test.ts +290 -0
  37. package/apps/gateway/src/adapters/redis-sentinel-publisher.ts +47 -0
  38. package/apps/gateway/src/adapters/sentinel-event-stream.ts +106 -0
  39. package/apps/gateway/src/agents/definitions-simple.ts +531 -0
  40. package/apps/gateway/src/agents/definitions.ts +297 -0
  41. package/apps/gateway/src/agents/local-model-provider.ts +219 -0
  42. package/apps/gateway/src/agents/tools.ts +163 -0
  43. package/apps/gateway/src/ai-providers/anthropic-provider.ts +194 -0
  44. package/apps/gateway/src/ai-providers/index.ts +10 -0
  45. package/apps/gateway/src/ai-providers/openai-provider.ts +193 -0
  46. package/apps/gateway/src/ai-providers/provider-manager.ts +160 -0
  47. package/apps/gateway/src/ai-providers/redis-credentials-store.ts +220 -0
  48. package/apps/gateway/src/ai-providers/types.ts +75 -0
  49. package/apps/gateway/src/config/persistence.ts +38 -0
  50. package/apps/gateway/src/crypto/envelope.ts +184 -0
  51. package/apps/gateway/src/db/prisma.ts +58 -0
  52. package/apps/gateway/src/db/redis.ts +95 -0
  53. package/apps/gateway/src/devkit/agents-api.ts +486 -0
  54. package/apps/gateway/src/devkit/metrics-parser.ts +152 -0
  55. package/apps/gateway/src/devkit/middleware.ts +53 -0
  56. package/apps/gateway/src/devkit/routes.ts +344 -0
  57. package/apps/gateway/src/devkit/tools-api.ts +251 -0
  58. package/apps/gateway/src/health/index.ts +257 -0
  59. package/apps/gateway/src/index.ts +1288 -0
  60. package/apps/gateway/src/metrics/index.ts +218 -0
  61. package/apps/gateway/src/middleware/auth.ts +118 -0
  62. package/apps/gateway/src/middleware/authApiKey.ts +156 -0
  63. package/apps/gateway/src/middleware/authJwt.ts +129 -0
  64. package/apps/gateway/src/middleware/correlationId.ts +36 -0
  65. package/apps/gateway/src/middleware/ddos-protection.ts +286 -0
  66. package/apps/gateway/src/middleware/errorHandler.ts +168 -0
  67. package/apps/gateway/src/middleware/mfa.ts +137 -0
  68. package/apps/gateway/src/middleware/rateLimit.ts +104 -0
  69. package/apps/gateway/src/middleware/rbac.ts +301 -0
  70. package/apps/gateway/src/middleware/security.ts +116 -0
  71. package/apps/gateway/src/middleware/validate.ts +194 -0
  72. package/apps/gateway/src/middleware/validate.ts.backup +153 -0
  73. package/apps/gateway/src/queue/config.ts +61 -0
  74. package/apps/gateway/src/queue/index.ts +229 -0
  75. package/apps/gateway/src/queue/processor.ts +461 -0
  76. package/apps/gateway/src/routes/ai-providers-simple.ts +166 -0
  77. package/apps/gateway/src/routes/ai-providers.ts +235 -0
  78. package/apps/gateway/src/routes/chats.ts +177 -0
  79. package/apps/gateway/src/routes/gdpr.ts +299 -0
  80. package/apps/gateway/src/routes/mfa.ts +254 -0
  81. package/apps/gateway/src/routes/sentinel-policies.ts +204 -0
  82. package/apps/gateway/src/routes/sentinel-predictive.ts +119 -0
  83. package/apps/gateway/src/routes/shield.ts +114 -0
  84. package/apps/gateway/src/routes/tool-credentials.ts +282 -0
  85. package/apps/gateway/src/routes/tool-proxy.ts +303 -0
  86. package/apps/gateway/src/runs/index.ts +34 -0
  87. package/apps/gateway/src/runs/memoryRunStore.ts +105 -0
  88. package/apps/gateway/src/runs/postgresRunStore.ts +186 -0
  89. package/apps/gateway/src/runs/runStore.ts +17 -0
  90. package/apps/gateway/src/runs/types.ts +58 -0
  91. package/apps/gateway/src/schemas/runs.ts +115 -0
  92. package/apps/gateway/src/types/fastify-rate-limit.d.ts +12 -0
  93. package/apps/gateway/src/utils/circuit-breaker.ts +134 -0
  94. package/apps/gateway/src/utils/log-encryption.ts +212 -0
  95. package/apps/gateway/test-all-individual.sh +69 -0
  96. package/apps/gateway/test-all-phases-final.sh +568 -0
  97. package/apps/gateway/test-all-phases-fixed-v2.sh +523 -0
  98. package/apps/gateway/test-all-phases-fixed.sh +385 -0
  99. package/apps/gateway/test-all-phases.sh +663 -0
  100. package/apps/gateway/test-concurrency.sh +60 -0
  101. package/apps/gateway/test-debug.sh +44 -0
  102. package/apps/gateway/test-e2e.sh +96 -0
  103. package/apps/gateway/test-extraction.sh +48 -0
  104. package/apps/gateway/test-full-operations-final.sh +328 -0
  105. package/apps/gateway/test-full-operations.sh +326 -0
  106. package/apps/gateway/test-idempotency-only.sh +108 -0
  107. package/apps/gateway/test-idempotency.sh +97 -0
  108. package/apps/gateway/test-individual.sh +126 -0
  109. package/apps/gateway/test-queue.sh +94 -0
  110. package/apps/gateway/tsconfig.json +32 -0
  111. package/apps/gateway/update-and-test.sh +36 -0
  112. package/dist/tui-handlers.js +174 -21
  113. package/dist/tui-handlers.js.map +1 -1
  114. package/mk3-tui/src/ui/layout.rs +1 -1
  115. package/package.json +5 -3
@@ -0,0 +1,11 @@
1
+ # Exclude client-side packages that aren't needed on server
2
+ packages/os-cli
3
+ packages/cli
4
+ packages/cli-tool
5
+ packages/cli-tool-standalone
6
+
7
+ # Exclude other client-side files
8
+ 4runr-os-enhanced.ts
9
+ 4runr-os.ts
10
+ run-os.ps1
11
+
@@ -0,0 +1,28 @@
1
+ {
2
+ "root": true,
3
+ "parser": "@typescript-eslint/parser",
4
+ "parserOptions": {
5
+ "ecmaVersion": 2022,
6
+ "sourceType": "module"
7
+ },
8
+ "plugins": ["@typescript-eslint"],
9
+ "extends": [
10
+ "eslint:recommended",
11
+ "plugin:@typescript-eslint/recommended"
12
+ ],
13
+ "rules": {
14
+ "@typescript-eslint/no-explicit-any": "off",
15
+ "@typescript-eslint/no-unused-vars": "warn",
16
+ "@typescript-eslint/no-non-null-assertion": "off"
17
+ },
18
+ "env": {
19
+ "node": true,
20
+ "es2022": true
21
+ },
22
+ "ignorePatterns": [
23
+ "**/__tests__/**",
24
+ "**/*.test.ts",
25
+ "dist/**"
26
+ ]
27
+ }
28
+
@@ -0,0 +1,122 @@
1
+ FROM node:20-slim AS base
2
+ WORKDIR /workspace
3
+ RUN apt-get update && apt-get install -y ca-certificates curl && rm -rf /var/lib/apt/lists/*
4
+
5
+ FROM base AS deps
6
+ RUN apt-get update && apt-get install -y python3 make g++ openssl libssl-dev && rm -rf /var/lib/apt/lists/*
7
+ COPY pnpm-workspace.yaml package.json tsconfig.json ./
8
+ # Copy .npmrc if it exists (optional - create empty if missing)
9
+ COPY apps/gateway ./apps/gateway
10
+ # Copy packages directory but we'll remove client-side ones immediately
11
+ COPY packages ./packages
12
+ # Remove client-side packages BEFORE pnpm reads the workspace (os-cli depends on unpublished @4runr/devkit)
13
+ RUN rm -rf packages/os-cli packages/cli packages/cli-tool packages/cli-tool-standalone packages/devkit || true
14
+ COPY prisma ./prisma
15
+ COPY src ./src
16
+ # Create .npmrc if it doesn't exist (pnpm will work without it)
17
+ RUN touch .npmrc || true
18
+ RUN corepack enable && corepack prepare pnpm@9.12.1 --activate
19
+ # Set node-linker to hoisted for flat node_modules structure
20
+ RUN pnpm config set node-linker hoisted
21
+ # Skip Prisma generation during install (we'll generate it in build if needed)
22
+ ENV PRISMA_SKIP_POSTINSTALL_GENERATE=1
23
+ RUN pnpm install --filter @4runr/shared --filter @4runr/sentinel --filter @4runr/gateway --workspace-root
24
+
25
+ FROM deps AS build
26
+ # Clean shared and sentinel package dists to ensure fresh build with updated types
27
+ RUN rm -rf packages/shared/dist packages/shared/.tsbuildinfo packages/sentinel/dist packages/sentinel/.tsbuildinfo
28
+ RUN pnpm --filter @4runr/shared run build
29
+ RUN pnpm --filter @4runr/sentinel run build
30
+ # Generate Prisma client before building gateway
31
+ # Use dummy DATABASE_URL for generation (just needs schema, not actual connection)
32
+ # Prisma schema is at /workspace/prisma/schema.prisma
33
+ RUN cd /workspace/apps/gateway && \
34
+ export DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" && \
35
+ pnpm db:generate && \
36
+ (test -d node_modules/.prisma/client || test -d ../../node_modules/.prisma/client) && \
37
+ echo "✓ Prisma client generated" || echo "✗ Prisma client generation failed"
38
+ # Compile root src/crypto folder (used by gateway routes)
39
+ # Create a temporary tsconfig for compiling just the crypto folder
40
+ RUN mkdir -p dist/src/crypto && \
41
+ echo '{"compilerOptions":{"target":"ES2022","module":"ES2022","moduleResolution":"bundler","esModuleInterop":true,"skipLibCheck":true,"outDir":"dist/src/crypto","rootDir":"src/crypto"},"include":["src/crypto/envelope.ts"]}' > /tmp/crypto-tsconfig.json && \
42
+ npx tsc --project /tmp/crypto-tsconfig.json 2>&1 | head -10 && \
43
+ test -f dist/src/crypto/envelope.js && echo "✓ Compiled envelope.js" || echo "⚠ envelope.js compilation may have failed"
44
+
45
+ RUN rm -rf apps/gateway/dist && \
46
+ pnpm --filter @4runr/gateway run build && \
47
+ (test -f apps/gateway/dist/index.js || test -f apps/gateway/dist/apps/gateway/src/index.js) && \
48
+ echo "✓ Gateway build completed successfully" || \
49
+ (echo "✗ Build failed: index.js not found" && exit 1)
50
+
51
+ FROM base AS runner
52
+ WORKDIR /workspace
53
+ RUN apt-get update && apt-get install -y openssl ca-certificates curl && rm -rf /var/lib/apt/lists/*
54
+ # Copy package files, config, and built artifacts
55
+ COPY --from=build /workspace/package.json ./package.json
56
+ COPY --from=build /workspace/pnpm-workspace.yaml ./pnpm-workspace.yaml
57
+ # .npmrc is optional - create empty if it doesn't exist
58
+ RUN touch .npmrc || true
59
+ COPY --from=build /workspace/packages ./packages
60
+ COPY --from=build /workspace/apps/gateway/package.json ./apps/gateway/package.json
61
+ COPY --from=build /workspace/apps/gateway/dist ./apps/gateway/dist
62
+ # Copy public directory (static files like dashboard)
63
+ COPY --from=deps /workspace/apps/gateway/public ./apps/gateway/public
64
+ # Copy node_modules from build stage (hoisted node-linker creates flat structure)
65
+ # Use --chown to set ownership during copy to avoid breaking symlinks
66
+ COPY --from=build --chown=node:node /workspace/node_modules ./node_modules
67
+ # Copy Prisma schema (needed for runtime client generation if needed)
68
+ COPY --from=build /workspace/prisma ./prisma
69
+ # Copy src directory (contains crypto and memory-db for AI providers)
70
+ COPY --from=build /workspace/src ./src
71
+ # Copy compiled crypto (if it was compiled)
72
+ RUN mkdir -p dist/src && (cp -r /workspace/dist/src/* dist/src/ 2>/dev/null || echo "Note: No compiled src found")
73
+ # Verify Prisma client was copied (could be in root node_modules with hoisted linker)
74
+ RUN (test -d node_modules/.prisma/client || test -d apps/gateway/node_modules/.prisma/client) && \
75
+ echo "✓ Prisma client found" || \
76
+ echo "⚠ Prisma client not found - checking..." && \
77
+ find node_modules -name ".prisma" -type d 2>/dev/null | head -5 || \
78
+ echo "⚠ Will generate Prisma client at runtime if needed"
79
+ # Verify shared package is accessible
80
+ RUN test -d packages/shared/dist && echo "✓ shared/dist exists" || echo "✗ shared/dist missing"
81
+ RUN test -f packages/shared/dist/index.js && echo "✓ shared/dist/index.js exists" || echo "✗ shared/dist/index.js missing"
82
+ # Check if @4runr/shared exists and if it's a symlink
83
+ # If not, create it (symlinks don't need special ownership)
84
+ RUN if [ ! -L node_modules/@4runr/shared ] && [ ! -d node_modules/@4runr/shared ]; then \
85
+ echo "✗ @4runr/shared NOT found in node_modules, creating symlink..."; \
86
+ mkdir -p node_modules/@4runr && \
87
+ ln -s ../../packages/shared node_modules/@4runr/shared && \
88
+ echo "✓ Created symlink: node_modules/@4runr/shared -> ../../packages/shared"; \
89
+ fi
90
+ # Verify the shared package's package.json exists (for ESM resolution)
91
+ RUN test -f node_modules/@4runr/shared/package.json && echo "✓ @4runr/shared/package.json exists" || echo "✗ @4runr/shared/package.json missing"
92
+ # Verify the package.json has the correct exports field for ESM resolution
93
+ RUN node -e "try { const pkg = require('./node_modules/@4runr/shared/package.json'); if (pkg.exports && pkg.exports['.']) { console.log('✓ package.json exports field configured correctly'); } else { console.error('✗ package.json missing exports field'); process.exit(1); } } catch(e) { console.error('✗ Error reading shared package.json:', e.message); process.exit(1); }"
94
+ # Verify Sentinel package is accessible
95
+ RUN test -d packages/sentinel/dist && echo "✓ sentinel/dist exists" || echo "✗ sentinel/dist missing"
96
+ RUN test -f packages/sentinel/dist/index.js && echo "✓ sentinel/dist/index.js exists" || echo "✗ sentinel/dist/index.js missing"
97
+ # Check if @4runr/sentinel exists and if it's a symlink
98
+ # If not, create it (symlinks don't need special ownership)
99
+ RUN if [ ! -L node_modules/@4runr/sentinel ] && [ ! -d node_modules/@4runr/sentinel ]; then \
100
+ echo "✗ @4runr/sentinel NOT found in node_modules, creating symlink..."; \
101
+ mkdir -p node_modules/@4runr && \
102
+ ln -s ../../packages/sentinel node_modules/@4runr/sentinel && \
103
+ echo "✓ Created symlink: node_modules/@4runr/sentinel -> ../../packages/sentinel"; \
104
+ fi
105
+ # Verify the Sentinel package's package.json exists (for ESM resolution)
106
+ RUN test -f node_modules/@4runr/sentinel/package.json && echo "✓ @4runr/sentinel/package.json exists" || echo "✗ @4runr/sentinel/package.json missing"
107
+ # Verify the package.json has the correct exports field for ESM resolution
108
+ RUN node -e "try { const pkg = require('./node_modules/@4runr/sentinel/package.json'); if (pkg.exports && pkg.exports['.']) { console.log('✓ Sentinel package.json exports field configured correctly'); } else { console.error('✗ Sentinel package.json missing exports field'); process.exit(1); } } catch(e) { console.error('✗ Error reading Sentinel package.json:', e.message); process.exit(1); }"
109
+ # Verify fastify exists (should be directly in node_modules with hoisted node-linker)
110
+ RUN test -d node_modules/fastify && echo "✓ fastify found in node_modules" || \
111
+ (echo "✗ fastify NOT found!" && ls -la node_modules/ | head -40)
112
+ RUN test -f node_modules/fastify/package.json && echo "✓ fastify package.json exists" || echo "✗ fastify package.json missing"
113
+ # Test Node.js ESM resolution from the gateway dist directory
114
+ RUN cd apps/gateway/dist && \
115
+ node --input-type=module -e "import('fastify').then(() => console.log('✓ Node.js can resolve fastify from dist')).catch(e => console.error('✗ Cannot resolve:', e.message))" || \
116
+ echo "Testing from workspace root:" && \
117
+ cd /workspace && \
118
+ node --input-type=module -e "import('fastify').then(() => console.log('✓ Node.js can resolve fastify from root')).catch(e => console.error('✗ Cannot resolve from root:', e.message))"
119
+ USER node
120
+ WORKDIR /workspace
121
+ # Run from workspace root - Node.js ESM should find node_modules at /workspace/node_modules
122
+ CMD ["node", "apps/gateway/dist/apps/gateway/src/index.js"]
@@ -0,0 +1,41 @@
1
+ FROM node:18-alpine AS base
2
+
3
+ # Install dependencies only when needed
4
+ FROM base AS deps
5
+ RUN apk add --no-cache libc6-compat
6
+ WORKDIR /app
7
+
8
+ # Install dependencies based on the preferred package manager
9
+ COPY package.json ./
10
+ RUN npm install
11
+
12
+ # Rebuild the source code only when needed
13
+ FROM base AS builder
14
+ WORKDIR /app
15
+ COPY --from=deps /app/node_modules ./node_modules
16
+ COPY . .
17
+
18
+ # Build the application
19
+ RUN npm run build
20
+
21
+ # Production image, copy all the files and run the app
22
+ FROM base AS runner
23
+ WORKDIR /app
24
+
25
+ ENV NODE_ENV=production
26
+
27
+ RUN addgroup --system --gid 1001 nodejs
28
+ RUN adduser --system --uid 1001 4runr
29
+
30
+ COPY --from=builder /app/dist ./dist
31
+ COPY --from=builder /app/node_modules ./node_modules
32
+ COPY --from=builder /app/package.json ./package.json
33
+
34
+ USER 4runr
35
+
36
+ EXPOSE 3000
37
+
38
+ ENV PORT=3000
39
+ ENV HOST=0.0.0.0
40
+
41
+ CMD ["node", "dist/index.js"]
@@ -0,0 +1,386 @@
1
+ #!/bin/bash
2
+ # Script to create the clean test-all-phases.sh file on the server
3
+
4
+ cat > /opt/4runr/gateway/test-all-phases.sh << 'EOFTEST'
5
+ #!/bin/bash
6
+ # Comprehensive test suite for all phases
7
+ # Tests Phase 1-6 features end-to-end
8
+
9
+ set +e # Don't exit on errors, we want to see all test results
10
+
11
+ GATEWAY_URL="${TEST_SERVER_URL:-http://localhost:3000}"
12
+ API_KEY="${TEST_API_KEY:-test-key-ce67dabb0d4e27e3d72877c921b89cae}"
13
+
14
+ # Colors for output
15
+ RED='\033[0;31m'
16
+ GREEN='\033[0;32m'
17
+ YELLOW='\033[1;33m'
18
+ NC='\033[0m' # No Color
19
+
20
+ # Test counters
21
+ TOTAL_TESTS=0
22
+ PASSED_TESTS=0
23
+ FAILED_TESTS=0
24
+
25
+ # Test result tracking
26
+ FAILED_TEST_NAMES=()
27
+
28
+ # Generate a valid UUID v4
29
+ generate_uuid() {
30
+ if command -v uuidgen > /dev/null 2>&1; then
31
+ uuidgen
32
+ else
33
+ cat /proc/sys/kernel/random/uuid 2>/dev/null || \
34
+ echo "$(od -x /dev/urandom | head -1 | awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' | tr '[:upper:]' '[:lower:]')"
35
+ fi
36
+ }
37
+
38
+ # Helper function to run a test
39
+ run_test() {
40
+ local test_name="$1"
41
+ local test_command="$2"
42
+
43
+ TOTAL_TESTS=$((TOTAL_TESTS + 1))
44
+ echo -n "Testing: $test_name... "
45
+
46
+ if eval "$test_command" > /tmp/test_output_$$.log 2>&1; then
47
+ echo -e "${GREEN}✓ PASSED${NC}"
48
+ PASSED_TESTS=$((PASSED_TESTS + 1))
49
+ return 0
50
+ else
51
+ echo -e "${RED}✗ FAILED${NC}"
52
+ FAILED_TESTS=$((FAILED_TESTS + 1))
53
+ FAILED_TEST_NAMES+=("$test_name")
54
+ echo " Error output:"
55
+ cat /tmp/test_output_$$.log | sed 's/^/ /'
56
+ return 1
57
+ fi
58
+ }
59
+
60
+ echo "========================================="
61
+ echo "4Runr Gateway - Comprehensive Phase Test"
62
+ echo "========================================="
63
+ echo ""
64
+ echo "Gateway URL: $GATEWAY_URL"
65
+ echo "API Key: ${API_KEY:0:20}..."
66
+ echo ""
67
+
68
+ # Wait for server to be ready
69
+ echo "Waiting for server to be ready..."
70
+ for i in {1..30}; do
71
+ if curl -sf "$GATEWAY_URL/health" > /dev/null 2>&1; then
72
+ echo "✓ Server is ready"
73
+ break
74
+ fi
75
+ if [ $i -eq 30 ]; then
76
+ echo "❌ Server is not ready after 30 seconds"
77
+ exit 1
78
+ fi
79
+ sleep 1
80
+ done
81
+
82
+ echo ""
83
+ echo "========================================="
84
+ echo "Phase 1-3: Core Features"
85
+ echo "========================================="
86
+ echo ""
87
+
88
+ # Phase 1: Idempotency
89
+ echo "--- Phase 1: Idempotency ---"
90
+ IDEMPOTENCY_KEY=$(generate_uuid)
91
+ IDEMPOTENT_RESPONSE=$(curl -sf --max-time 5 -X POST "$GATEWAY_URL/api/runs" \
92
+ -H "x-api-key: $API_KEY" \
93
+ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
94
+ -H "Content-Type: application/json" \
95
+ -d '{"name":"Idempotency Test","input":{"test":"idempotency"}}' 2>/dev/null)
96
+
97
+ run_test "Idempotency: Create run with idempotency key" \
98
+ "echo \"$IDEMPOTENT_RESPONSE\" | grep -q '\"run\"\|\"success\"'"
99
+
100
+ FIRST_RUN_ID=$(echo "$IDEMPOTENT_RESPONSE" | grep -o '"run":{[^}]*"id":"[^"]*' | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
101
+ if [ -z "$FIRST_RUN_ID" ]; then
102
+ FIRST_RUN_ID=$(echo "$IDEMPOTENT_RESPONSE" | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
103
+ fi
104
+
105
+ if [ ! -z "$FIRST_RUN_ID" ]; then
106
+ SECOND_RESPONSE=$(curl -sf --max-time 5 -X POST "$GATEWAY_URL/api/runs" \
107
+ -H "x-api-key: $API_KEY" \
108
+ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
109
+ -H "Content-Type: application/json" \
110
+ -d '{"name":"Idempotency Test","input":{"test":"idempotency"}}' 2>/dev/null)
111
+
112
+ SECOND_RUN_ID=$(echo "$SECOND_RESPONSE" | grep -o '"run":{[^}]*"id":"[^"]*' | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
113
+ if [ -z "$SECOND_RUN_ID" ]; then
114
+ SECOND_RUN_ID=$(echo "$SECOND_RESPONSE" | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
115
+ fi
116
+
117
+ run_test "Idempotency: Duplicate request returns same run" \
118
+ "[ ! -z \"$SECOND_RUN_ID\" ] && [ \"$FIRST_RUN_ID\" = \"$SECOND_RUN_ID\" ]"
119
+ fi
120
+
121
+ # Phase 2: Structured Logging & Metrics
122
+ echo ""
123
+ echo "--- Phase 2: Logging & Metrics ---"
124
+ run_test "Metrics: Prometheus metrics endpoint accessible" \
125
+ "curl -sf '$GATEWAY_URL/metrics' | grep -q 'http_request_total'"
126
+
127
+ run_test "Metrics: Metrics include run metrics" \
128
+ "curl -sf '$GATEWAY_URL/metrics' | grep -q 'runs_created_total'"
129
+
130
+ # Phase 3: Queue System
131
+ echo ""
132
+ echo "--- Phase 3: Queue System ---"
133
+ QUEUE_RESPONSE=$(curl -sf --max-time 5 -X POST "$GATEWAY_URL/api/runs" \
134
+ -H "x-api-key: $API_KEY" \
135
+ -H "Content-Type: application/json" \
136
+ -d '{"name":"Queue Test","input":{"test":"queue"}}' 2>/dev/null)
137
+
138
+ QUEUE_RUN_ID=$(echo "$QUEUE_RESPONSE" | grep -o '"run":{[^}]*"id":"[^"]*' | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
139
+ if [ -z "$QUEUE_RUN_ID" ]; then
140
+ QUEUE_RUN_ID=$(echo "$QUEUE_RESPONSE" | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
141
+ fi
142
+
143
+ run_test "Queue: Create run successfully" \
144
+ "[ ! -z \"$QUEUE_RUN_ID\" ]"
145
+
146
+ if [ ! -z "$QUEUE_RUN_ID" ]; then
147
+ START_RESPONSE=$(curl -sf --max-time 5 -X POST "$GATEWAY_URL/api/runs/$QUEUE_RUN_ID/start" \
148
+ -H "x-api-key: $API_KEY" \
149
+ -H "Content-Type: application/json" \
150
+ -d '{"priority":"high"}' 2>/dev/null)
151
+
152
+ run_test "Queue: Start run (should queue)" \
153
+ "echo \"$START_RESPONSE\" | grep -q 'queued\|success'"
154
+
155
+ sleep 2
156
+
157
+ STATUS_RESPONSE=$(curl -sf --max-time 5 "$GATEWAY_URL/api/runs/$QUEUE_RUN_ID" \
158
+ -H "x-api-key: $API_KEY" 2>/dev/null)
159
+
160
+ run_test "Queue: Run status updated" \
161
+ "echo \"$STATUS_RESPONSE\" | grep -q '\"status\"'"
162
+ fi
163
+
164
+ echo ""
165
+ echo "========================================="
166
+ echo "Phase 4: Production Hardening"
167
+ echo "========================================="
168
+ echo ""
169
+
170
+ # Phase 4.1: Security Headers
171
+ echo "--- Phase 4.1: Security Headers ---"
172
+ run_test "Security: Content-Security-Policy header present" \
173
+ "curl -sf -I '$GATEWAY_URL/health' | grep -qi 'content-security-policy'"
174
+
175
+ run_test "Security: X-Frame-Options header present" \
176
+ "curl -sf -I '$GATEWAY_URL/health' | grep -qi 'x-frame-options'"
177
+
178
+ run_test "Security: X-Content-Type-Options header present" \
179
+ "curl -sf -I '$GATEWAY_URL/health' | grep -qi 'x-content-type-options'"
180
+
181
+ run_test "Security: Server header removed" \
182
+ "! curl -sf -I '$GATEWAY_URL/health' | grep -qi '^server:'"
183
+
184
+ # Phase 4.2: Error Handling
185
+ echo ""
186
+ echo "--- Phase 4.2: Error Handling ---"
187
+ run_test "Error Handling: 404 error includes correlation ID" \
188
+ "curl -s '$GATEWAY_URL/api/runs/00000000-0000-0000-0000-000000000000' \
189
+ -H 'x-api-key: $API_KEY' | grep -q 'error'"
190
+
191
+ run_test "Error Handling: Validation error includes details" \
192
+ "curl -s -X POST '$GATEWAY_URL/api/runs' \
193
+ -H 'x-api-key: $API_KEY' \
194
+ -H 'Content-Type: application/json' \
195
+ -d '{\"input\":{}}' | grep -q 'details'"
196
+
197
+ run_test "Error Handling: Error response includes timestamp" \
198
+ "curl -s '$GATEWAY_URL/api/runs/invalid-id' \
199
+ -H 'x-api-key: $API_KEY' | grep -q 'timestamp'"
200
+
201
+ # Phase 4.3: Health Checks
202
+ echo ""
203
+ echo "--- Phase 4.3: Health Checks ---"
204
+ run_test "Health: Liveness endpoint returns 200" \
205
+ "curl -sf '$GATEWAY_URL/health' | grep -q '\"status\"'"
206
+
207
+ run_test "Health: Readiness endpoint returns 200" \
208
+ "curl -sf '$GATEWAY_URL/ready' | grep -q '\"ready\"'"
209
+
210
+ run_test "Health: Readiness includes dependency checks" \
211
+ "curl -sf '$GATEWAY_URL/ready' | grep -q '\"checks\"'"
212
+
213
+ run_test "Health: Startup endpoint accessible" \
214
+ "curl -sf '$GATEWAY_URL/startup' | grep -q '\"started\"'"
215
+
216
+ echo ""
217
+ echo "========================================="
218
+ echo "Phase 5: Testing & Validation"
219
+ echo "========================================="
220
+ echo ""
221
+
222
+ # Phase 5: Integration Tests
223
+ echo "--- Phase 5: Integration Tests ---"
224
+ run_test "Integration: Can run integration tests" \
225
+ "cd /opt/4runr/gateway && pnpm test:integration -- simple.test.ts 2>&1 | grep -q 'PASS'"
226
+
227
+ echo ""
228
+ echo "========================================="
229
+ echo "End-to-End Workflow Tests"
230
+ echo "========================================="
231
+ echo ""
232
+
233
+ # Complete Workflow
234
+ echo "--- Complete Run Workflow ---"
235
+ WORKFLOW_RESPONSE=$(curl -sf -X POST "$GATEWAY_URL/api/runs" \
236
+ -H "x-api-key: $API_KEY" \
237
+ -H "Content-Type: application/json" \
238
+ -d '{"name":"E2E Workflow Test","input":{"test":"workflow"}}' 2>/dev/null)
239
+
240
+ # Extract run ID - try multiple patterns
241
+ WORKFLOW_RUN_ID=$(echo "$WORKFLOW_RESPONSE" | grep -o '"run"[^}]*"id":"[^"]*' | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
242
+ if [ -z "$WORKFLOW_RUN_ID" ]; then
243
+ # Try simpler pattern
244
+ WORKFLOW_RUN_ID=$(echo "$WORKFLOW_RESPONSE" | grep -o '"id":"[a-f0-9-]\{36\}' | cut -d'"' -f4 | head -1)
245
+ fi
246
+
247
+ run_test "E2E: Create run" \
248
+ "[ ! -z \"$WORKFLOW_RUN_ID\" ]"
249
+
250
+ if [ ! -z "$WORKFLOW_RUN_ID" ]; then
251
+ run_test "E2E: Start run" \
252
+ "curl -sf -X POST '$GATEWAY_URL/api/runs/$WORKFLOW_RUN_ID/start' \
253
+ -H 'x-api-key: $API_KEY' \
254
+ -H 'Content-Type: application/json' \
255
+ -d '{\"priority\":\"normal\"}' | grep -q '\"queued\"'"
256
+
257
+ run_test "E2E: Get run status" \
258
+ "curl -sf '$GATEWAY_URL/api/runs/$WORKFLOW_RUN_ID' \
259
+ -H 'x-api-key: $API_KEY' | grep -q '\"status\"'"
260
+
261
+ run_test "E2E: List runs" \
262
+ "curl -sf '$GATEWAY_URL/api/runs' \
263
+ -H 'x-api-key: $API_KEY' | grep -q '\"runs\"'"
264
+ fi
265
+
266
+ # Authentication Tests
267
+ echo ""
268
+ echo "--- Authentication Tests ---"
269
+ run_test "Auth: API key authentication required" \
270
+ "curl -sf '$GATEWAY_URL/api/runs' | grep -q '401\|403\|Unauthorized'"
271
+
272
+ run_test "Auth: Valid API key works" \
273
+ "curl -sf '$GATEWAY_URL/api/runs' \
274
+ -H 'x-api-key: $API_KEY' | grep -q '\"runs\"'"
275
+
276
+ run_test "Auth: Health endpoint doesn't require auth" \
277
+ "curl -sf '$GATEWAY_URL/health' | grep -q '\"status\"'"
278
+
279
+ # Rate Limiting Tests
280
+ echo ""
281
+ echo "--- Rate Limiting Tests ---"
282
+ run_test "Rate Limit: Health endpoint bypasses rate limiting" \
283
+ "for i in {1..20}; do curl -sf '$GATEWAY_URL/health' > /dev/null; done && true"
284
+
285
+ # Concurrent Operations
286
+ echo ""
287
+ echo "--- Concurrent Operations ---"
288
+ CONCURRENT_SUCCESS=0
289
+ for i in {1..5}; do
290
+ RESPONSE=$(curl -s --max-time 5 -X POST "$GATEWAY_URL/api/runs" \
291
+ -H "x-api-key: $API_KEY" \
292
+ -H "Content-Type: application/json" \
293
+ -d "{\"name\":\"Concurrent Test $i\",\"input\":{\"index\":$i}}" 2>/dev/null)
294
+ if [ ! -z "$RESPONSE" ] && (echo "$RESPONSE" | grep -q '"run"\|"success"'); then
295
+ CONCURRENT_SUCCESS=$((CONCURRENT_SUCCESS + 1))
296
+ fi
297
+ done
298
+
299
+ run_test "Concurrency: Multiple runs can be created concurrently" \
300
+ "[ $CONCURRENT_SUCCESS -ge 3 ]"
301
+
302
+ # Priority Queue
303
+ echo ""
304
+ echo "--- Priority Queue ---"
305
+ PRIORITY_RUN1_RESPONSE=$(curl -sf -X POST "$GATEWAY_URL/api/runs" \
306
+ -H "x-api-key: $API_KEY" \
307
+ -H "Content-Type: application/json" \
308
+ -d '{"name":"Normal Priority","input":{"priority":"normal"}}' 2>/dev/null)
309
+
310
+ PRIORITY_RUN1=$(echo "$PRIORITY_RUN1_RESPONSE" | grep -o '"run":{[^}]*"id":"[^"]*' | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
311
+ if [ -z "$PRIORITY_RUN1" ]; then
312
+ PRIORITY_RUN1=$(echo "$PRIORITY_RUN1_RESPONSE" | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
313
+ fi
314
+
315
+ PRIORITY_RUN2_RESPONSE=$(curl -sf -X POST "$GATEWAY_URL/api/runs" \
316
+ -H "x-api-key: $API_KEY" \
317
+ -H "Content-Type: application/json" \
318
+ -d '{"name":"High Priority","input":{"priority":"high"}}' 2>/dev/null)
319
+
320
+ PRIORITY_RUN2=$(echo "$PRIORITY_RUN2_RESPONSE" | grep -o '"run":{[^}]*"id":"[^"]*' | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
321
+ if [ -z "$PRIORITY_RUN2" ]; then
322
+ PRIORITY_RUN2=$(echo "$PRIORITY_RUN2_RESPONSE" | grep -o '"id":"[^"]*' | cut -d'"' -f4 | head -1)
323
+ fi
324
+
325
+ if [ ! -z "$PRIORITY_RUN1" ] && [ ! -z "$PRIORITY_RUN2" ]; then
326
+ curl -sf -X POST "$GATEWAY_URL/api/runs/$PRIORITY_RUN1/start" \
327
+ -H "x-api-key: $API_KEY" \
328
+ -H "Content-Type: application/json" \
329
+ -d '{"priority":"normal"}' > /dev/null
330
+
331
+ curl -sf -X POST "$GATEWAY_URL/api/runs/$PRIORITY_RUN2/start" \
332
+ -H "x-api-key: $API_KEY" \
333
+ -H "Content-Type: application/json" \
334
+ -d '{"priority":"high"}' > /dev/null
335
+
336
+ sleep 2
337
+
338
+ run_test "Priority: Both runs started successfully" \
339
+ "curl -sf '$GATEWAY_URL/api/runs/$PRIORITY_RUN1' -H 'x-api-key: $API_KEY' | grep -q '\"status\"' && \
340
+ curl -sf '$GATEWAY_URL/api/runs/$PRIORITY_RUN2' -H 'x-api-key: $API_KEY' | grep -q '\"status\"'"
341
+ fi
342
+
343
+ # Error Scenarios
344
+ echo ""
345
+ echo "--- Error Scenarios ---"
346
+ run_test "Errors: Invalid run ID returns 404" \
347
+ "curl -s '$GATEWAY_URL/api/runs/00000000-0000-0000-0000-000000000000' \
348
+ -H 'x-api-key: $API_KEY' | grep -q 'error'"
349
+
350
+ run_test "Errors: Invalid UUID format returns 400" \
351
+ "curl -s '$GATEWAY_URL/api/runs/invalid-id' \
352
+ -H 'x-api-key: $API_KEY' | grep -q 'error'"
353
+
354
+ run_test "Errors: Missing required field returns 400" \
355
+ "curl -s -X POST '$GATEWAY_URL/api/runs' \
356
+ -H 'x-api-key: $API_KEY' \
357
+ -H 'Content-Type: application/json' \
358
+ -d '{\"input\":{}}' | grep -q 'error'"
359
+
360
+ echo ""
361
+ echo "========================================="
362
+ echo "Test Summary"
363
+ echo "========================================="
364
+ echo ""
365
+ echo "Total Tests: $TOTAL_TESTS"
366
+ echo -e "${GREEN}Passed: $PASSED_TESTS${NC}"
367
+ echo -e "${RED}Failed: $FAILED_TESTS${NC}"
368
+ echo ""
369
+
370
+ if [ $FAILED_TESTS -gt 0 ]; then
371
+ echo -e "${RED}Failed Tests:${NC}"
372
+ for test_name in "${FAILED_TEST_NAMES[@]}"; do
373
+ echo " - $test_name"
374
+ done
375
+ echo ""
376
+ echo -e "${RED}❌ Some tests failed. Please review the errors above.${NC}"
377
+ exit 1
378
+ else
379
+ echo -e "${GREEN}✅ All tests passed!${NC}"
380
+ exit 0
381
+ fi
382
+ EOFTEST
383
+
384
+ chmod +x /opt/4runr/gateway/test-all-phases.sh
385
+ echo "✓ Clean test-all-phases.sh created successfully"
386
+