diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 1be77514..79ff70e9 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -197,3 +197,60 @@ jobs: flags: integration-v4-sm name: codecov-integration-v4-sm-node-${{ matrix.node-version }}-shard-${{ matrix.shard }} fail_ci_if_error: false + + # Integration tests (OSS): spins up Conductor OSS + Postgres via + # scripts/docker-compose-oss.yaml and runs the integration suite unauthenticated, + # with Orkes-only tests gated out via CONDUCTOR_SERVER_TYPE=oss (see + # test:integration:oss). The same stack can be run locally with + # scripts/run-integration-oss.sh. + integration-tests-oss: + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + node-version: [20, 22, 24] + name: Node.js v${{ matrix.node-version }} - integration oss + env: + CONDUCTOR_SERVER_URL: http://localhost:8080/api + CONDUCTOR_SERVER_TYPE: oss + CONDUCTOR_REQUEST_TIMEOUT_MS: "120000" + CONDUCTOR_RETRY_SERVER_ERRORS: "true" + HTTPBIN_SERVICE_HOSTNAME: httpbin + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - name: Cache node_modules + id: cache + uses: actions/cache@v4 + with: + path: node_modules + key: npm-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }} + restore-keys: | + npm-${{ matrix.node-version }}- + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: npm ci + - name: Start Conductor OSS stack + run: docker compose -f scripts/docker-compose-oss.yaml up -d + - name: Wait for Conductor to be healthy + run: timeout 120 bash -c 'until curl -sf http://localhost:8080/health; do sleep 5; done' + - name: Run integration tests (OSS) + run: npm run test:integration:oss -- --ci --runInBand --testTimeout=120000 --reporters=default --reporters=github-actions --reporters=jest-junit + env: + JEST_JUNIT_OUTPUT_NAME: integration-oss-node-${{ matrix.node-version }}-test-results.xml + - name: Dump Conductor logs + if: failure() + run: docker compose -f scripts/docker-compose-oss.yaml logs conductor-server + - name: Publish Test Results + uses: dorny/test-reporter@v2 + if: ${{ !cancelled() }} + with: + name: integration oss (Node ${{ matrix.node-version }}) + path: reports/integration-oss-node-${{ matrix.node-version }}-test-results.xml + reporter: jest-junit diff --git a/package.json b/package.json index b125bd90..3d3a8b8d 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "test:integration:base": "jest --force-exit --detectOpenHandles --testMatch='**/src/integration-tests/*.test.[jt]s?(x)'", "test:integration:v5": "cross-env ORKES_BACKEND_VERSION=5 npm run test:integration:base --", "test:integration:v4": "cross-env ORKES_BACKEND_VERSION=4 npm run test:integration:base --", + "test:integration:oss": "cross-env ORKES_BACKEND_VERSION=5 CONDUCTOR_SERVER_TYPE=oss npm run test:integration:base --", "test:integration:v5:batch": "cross-env ORKES_BACKEND_VERSION=5 node scripts/run-integration-batch.mjs", "test:integration:v4:batch": "cross-env ORKES_BACKEND_VERSION=4 node scripts/run-integration-batch.mjs", "ci": "npm run lint && npm run test", diff --git a/scripts/docker-compose-oss.yaml b/scripts/docker-compose-oss.yaml new file mode 100644 index 00000000..012a750f --- /dev/null +++ b/scripts/docker-compose-oss.yaml @@ -0,0 +1,37 @@ +# Conductor OSS stack used to run the SDK integration tests against open-source +# Conductor. Shared by scripts/run-integration-oss.sh and the +# integration-tests-oss job in .github/workflows/pull_request.yml. +# +# The Conductor server reaches httpbin over the compose network at +# http://httpbin:8081, which matches HTTPBIN_SERVICE_HOSTNAME=httpbin. +services: + conductor-server: + image: conductoross/conductor:latest + environment: + - CONFIG_PROP=config-postgres.properties + ports: + - "8080:8080" + healthcheck: + test: ["CMD", "curl", "-I", "-XGET", "http://localhost:8080/health"] + interval: 10s + timeout: 10s + retries: 20 + links: + - conductor-postgres:postgresdb + depends_on: + conductor-postgres: + condition: service_healthy + conductor-postgres: + image: postgres:16 + environment: + - POSTGRES_USER=conductor + - POSTGRES_PASSWORD=conductor + healthcheck: + test: timeout 5 bash -c 'cat < /dev/null > /dev/tcp/localhost/5432' + interval: 5s + timeout: 5s + retries: 12 + httpbin: + image: ghcr.io/orkes-io/test-utils/httpbin:latest + expose: + - "8081" diff --git a/scripts/run-integration-oss.sh b/scripts/run-integration-oss.sh new file mode 100755 index 00000000..54945727 --- /dev/null +++ b/scripts/run-integration-oss.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# +# Spin up a local Conductor OSS stack and run the SDK integration suite against +# it, mirroring the `integration-tests-oss` job in +# .github/workflows/pull_request.yml. Orkes-only tests are gated out via +# CONDUCTOR_SERVER_TYPE=oss (see the test:integration:oss npm script). +# +# The stack (Conductor OSS + Postgres + httpbin) is defined in +# scripts/docker-compose-oss.yaml and is torn down automatically on exit. +# +# Test output is written to both stdout and a log file (default: +# scripts/oss-test-run.log, override with -l|--log) so it can be shared later. +# +# Usage: +# scripts/run-integration-oss.sh [-t|--test ] [-l|--log ] [--keep-up] [-- jest args] +# Examples: +# scripts/run-integration-oss.sh # full OSS-gated suite +# scripts/run-integration-oss.sh --test WorkflowExecutor +# scripts/run-integration-oss.sh --log /tmp/oss.log # custom log path +# scripts/run-integration-oss.sh --keep-up # leave the stack running afterwards +# scripts/run-integration-oss.sh -- --testPathPatterns="EventClient" +set -euo pipefail + +TEST_PATTERN="" +KEEP_UP=0 +LOG_FILE="" + +# Print the leading comment block (everything after the shebang up to the first +# non-comment line) as help text, so it stays in sync with the header above. +usage() { awk 'NR>1 && /^#/ {sub(/^# ?/, ""); print; next} NR>1 {exit}' "$0"; } + +extra=() +while [[ $# -gt 0 ]]; do + case "$1" in + -t|--test) TEST_PATTERN="${2:?--test needs a path or pattern}"; shift 2 ;; + -l|--log) LOG_FILE="${2:?--log needs a file path}"; shift 2 ;; + --keep-up) KEEP_UP=1; shift ;; + -h|--help) usage; exit 0 ;; + --) shift; extra=("$@"); break ;; + *) echo "Unknown argument: $1" >&2; usage; exit 1 ;; + esac +done + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +COMPOSE_FILE="${SCRIPT_DIR}/docker-compose-oss.yaml" +LOG_FILE="${LOG_FILE:-${SCRIPT_DIR}/oss-test-run.log}" +cd "${REPO_ROOT}" + +# Guard: this script spins up and tests against a *local* OSS stack. If enterprise +# / remote server vars are already in the environment they would silently redirect +# the suite at a deployed server (e.g. sdkdev) and make Orkes-only tests pass, +# defeating the purpose of the OSS run. Refuse to run unless explicitly overridden. +if [[ "${ALLOW_REMOTE_SERVER:-0}" != "1" ]]; then + offending=() + [[ -n "${CONDUCTOR_AUTH_KEY:-}" ]] && offending+=("CONDUCTOR_AUTH_KEY") + [[ -n "${CONDUCTOR_AUTH_SECRET:-}" ]] && offending+=("CONDUCTOR_AUTH_SECRET") + if [[ -n "${CONDUCTOR_SERVER_URL:-}" \ + && ! "${CONDUCTOR_SERVER_URL}" =~ ^https?://(localhost|127\.0\.0\.1)(:|/|$) ]]; then + offending+=("CONDUCTOR_SERVER_URL=${CONDUCTOR_SERVER_URL}") + fi + if (( ${#offending[@]} > 0 )); then + echo "Error: refusing to run the OSS suite — remote/enterprise server vars are set:" >&2 + printf ' - %s\n' "${offending[@]}" >&2 + echo >&2 + echo "This script runs against a LOCAL OSS stack. Unset these first:" >&2 + echo " unset CONDUCTOR_SERVER_URL CONDUCTOR_AUTH_KEY CONDUCTOR_AUTH_SECRET" >&2 + echo "Or set ALLOW_REMOTE_SERVER=1 to bypass this check intentionally." >&2 + exit 1 + fi +fi + +CONDUCTOR_SERVER_URL="${CONDUCTOR_SERVER_URL:-http://localhost:8080/api}" +HEALTH_URL="${CONDUCTOR_SERVER_URL%/api}/health" + +compose() { docker compose -f "${COMPOSE_FILE}" "$@"; } + +cleanup() { + if [[ "${KEEP_UP}" == "1" ]]; then + echo "--keep-up set: leaving the OSS stack running. Tear down with:" + echo " docker compose -f ${COMPOSE_FILE} down -v" + return + fi + echo "Tearing down Conductor OSS stack..." + compose down -v || true +} +trap cleanup EXIT + +echo "Starting Conductor OSS stack (${COMPOSE_FILE})..." +compose up -d + +echo "Waiting for Conductor to be healthy at ${HEALTH_URL} ..." +# Portable wait loop using bash's built-in SECONDS (macOS has no `timeout`). +HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-180}" +deadline=$(( SECONDS + HEALTH_TIMEOUT )) +until curl -sf "${HEALTH_URL}" >/dev/null 2>&1; do + if (( SECONDS >= deadline )); then + echo "Error: Conductor did not become healthy within ${HEALTH_TIMEOUT}s." >&2 + compose logs conductor-server || true + exit 1 + fi + sleep 5 +done +echo "Conductor is up." + +export CONDUCTOR_SERVER_URL +export CONDUCTOR_SERVER_TYPE=oss +export HTTPBIN_SERVICE_HOSTNAME="${HTTPBIN_SERVICE_HOSTNAME:-httpbin}" +export CONDUCTOR_REQUEST_TIMEOUT_MS="${CONDUCTOR_REQUEST_TIMEOUT_MS:-120000}" +export CONDUCTOR_RETRY_SERVER_ERRORS="${CONDUCTOR_RETRY_SERVER_ERRORS:-true}" + +test_cmd=(npm run test:integration:oss -- --ci --runInBand --testTimeout=120000) +if [[ -n "${TEST_PATTERN}" ]]; then + # Targeting a specific test: pass it straight through as a path pattern. + test_cmd+=("--testPathPatterns=${TEST_PATTERN}") +fi +if (( ${#extra[@]} > 0 )); then + test_cmd+=("${extra[@]}") +fi + +if [[ -n "${TEST_PATTERN}" ]]; then + echo "Running OSS integration tests | test '${TEST_PATTERN}' | server ${CONDUCTOR_SERVER_URL}" +else + echo "Running OSS integration tests | full OSS-gated suite | server ${CONDUCTOR_SERVER_URL}" +fi +echo "Writing output to ${LOG_FILE}" + +# Tee to a log file for later sharing. pipefail ensures the test command's exit +# status (not tee's) is what propagates. +"${test_cmd[@]}" 2>&1 | tee "${LOG_FILE}" diff --git a/src/integration-tests/ApplicationClient.test.ts b/src/integration-tests/ApplicationClient.test.ts index f94166fd..6b6dde4e 100644 --- a/src/integration-tests/ApplicationClient.test.ts +++ b/src/integration-tests/ApplicationClient.test.ts @@ -9,10 +9,10 @@ import { import { ApplicationClient } from "../sdk"; import { createClientWithRetry } from "./utils/createClientWithRetry"; import type { Tag } from "../open-api"; -import { describeForOrkesV4 } from "./utils/customJestDescribe"; +import { describeForOrkesOnlyV4 } from "./utils/customJestDescribe"; describe("ApplicationClient", () => { - describeForOrkesV4("ApplicationClient V4+", () => { + describeForOrkesOnlyV4("ApplicationClient V4+", () => { jest.setTimeout(60000); let applicationClient: ApplicationClient; @@ -578,5 +578,5 @@ describe("ApplicationClient", () => { // ).rejects.toThrow(); // }); }); - }); // end describeForOrkesV4 + }); // end describeForOrkesOnlyV4 }); diff --git a/src/integration-tests/AuthorizationClient.test.ts b/src/integration-tests/AuthorizationClient.test.ts index 5bbe0a9d..1cbed46d 100644 --- a/src/integration-tests/AuthorizationClient.test.ts +++ b/src/integration-tests/AuthorizationClient.test.ts @@ -5,7 +5,7 @@ import { MetadataClient, } from "../sdk"; import { createClientWithRetry } from "./utils/createClientWithRetry"; -import { describeForOrkesV4 } from "./utils/customJestDescribe"; +import { describeForOrkesOnlyV4 } from "./utils/customJestDescribe"; import { registerWorkflowDefWithRetry } from "./utils/registerWorkflowWithRetry"; /** @@ -15,7 +15,7 @@ import { registerWorkflowDefWithRetry } from "./utils/registerWorkflowWithRetry" * in a lifecycle order: create → read → update → delete. */ describe("AuthorizationClient", () => { - describeForOrkesV4("AuthorizationClient V4+", () => { + describeForOrkesOnlyV4("AuthorizationClient V4+", () => { jest.setTimeout(60000); const suffix = Date.now(); @@ -355,5 +355,5 @@ describe("AuthorizationClient", () => { ).rejects.toThrow(); }); }); - }); // end describeForOrkesV4 + }); // end describeForOrkesOnlyV4 }); diff --git a/src/integration-tests/EventClient.test.ts b/src/integration-tests/EventClient.test.ts index d26f9437..f5f1ce04 100644 --- a/src/integration-tests/EventClient.test.ts +++ b/src/integration-tests/EventClient.test.ts @@ -14,7 +14,10 @@ import type { } from "../open-api"; import { EventClient } from "../sdk"; import { createClientWithRetry } from "./utils/createClientWithRetry"; -import { describeForOrkesV4, describeForOrkesV5 } from "./utils/customJestDescribe"; +import { + describeForOrkesOnlyV4, + describeForOrkesOnlyV5, +} from "./utils/customJestDescribe"; import { pollUntil } from "./utils/pollUntil"; const TEST_HANDLER_NAME_PREFIX = "jsSdkTest:"; @@ -79,7 +82,7 @@ describe("EventClient", () => { description: `Test event handler: ${name}`, }); - describeForOrkesV4("Event Handler Management", () => { + describeForOrkesOnlyV4("Event Handler Management", () => { test("Should add a single event handler", async () => { const handlerName = createUniqueName("event-handler"); const eventName = createUniqueName("event"); @@ -312,7 +315,7 @@ describe("EventClient", () => { }); }); - describeForOrkesV4("Tag Management", () => { + describeForOrkesOnlyV4("Tag Management", () => { test("Should get tags for an event handler", async () => { const handlerName = createUniqueName("event-handler"); const eventName = createUniqueName("event"); @@ -483,7 +486,7 @@ describe("EventClient", () => { }); }); - describeForOrkesV4("Test Endpoint", () => { + describeForOrkesOnlyV4("Test Endpoint", () => { test("Should call test endpoint", async () => { const result = await eventClient.test(); @@ -575,7 +578,7 @@ describe("EventClient", () => { }); }); - describeForOrkesV5("Event Processing", () => { + describeForOrkesOnlyV5("Event Processing", () => { test("Should handle incoming event", async () => { const handlerName = createUniqueName("event-handler"); const eventName = createUniqueName("event"); @@ -666,7 +669,7 @@ describe("EventClient", () => { }); }); - describeForOrkesV5("Event Executions and Statistics", () => { + describeForOrkesOnlyV5("Event Executions and Statistics", () => { test("Should get all active event handlers (execution view)", async () => { const handlerName = createUniqueName("event-handler"); diff --git a/src/integration-tests/MetadataClient.complete.test.ts b/src/integration-tests/MetadataClient.complete.test.ts index 23892394..7ae95000 100644 --- a/src/integration-tests/MetadataClient.complete.test.ts +++ b/src/integration-tests/MetadataClient.complete.test.ts @@ -15,7 +15,11 @@ import { OrkesClients, } from "../sdk"; import { createClientWithRetry } from "./utils/createClientWithRetry"; -import { describeForOrkesV4 } from "./utils/customJestDescribe"; +import { + describeForOrkesOnly, + describeForOrkesOnlyV4, + describeForOssSchedulerWip, +} from "./utils/customJestDescribe"; import { registerWorkflowDefWithRetry } from "./utils/registerWorkflowWithRetry"; /** @@ -168,7 +172,7 @@ describe("MetadataClient Complete Coverage", () => { // ==================== Workflow Tags ==================== - describeForOrkesV4("Workflow Tags", () => { + describeForOrkesOnlyV4("Workflow Tags", () => { test("addWorkflowTag should add a tag to a workflow definition", async () => { await expect( metadataClient.addWorkflowTag( @@ -222,7 +226,7 @@ describe("MetadataClient Complete Coverage", () => { // ==================== Task Tags ==================== - describeForOrkesV4("Task Tags", () => { + describeForOrkesOnlyV4("Task Tags", () => { test("addTaskTag should add a tag to a task definition", async () => { await expect( metadataClient.addTaskTag( @@ -276,7 +280,7 @@ describe("MetadataClient Complete Coverage", () => { // ==================== Rate Limits ==================== // Rate limit API may not be available on all server versions - describe("Rate Limits", () => { + describeForOrkesOnly("Rate Limits", () => { let rateLimitSupported = true; test("setWorkflowRateLimit should configure rate limiting", async () => { @@ -320,7 +324,7 @@ describe("MetadataClient Complete Coverage", () => { // ==================== Scheduler Extended ==================== - describeForOrkesV4("Scheduler Extended", () => { + describeForOssSchedulerWip("Scheduler Extended", () => { beforeAll(async () => { if (!schedulerClient) { console.warn("schedulerClient is undefined (client creation likely failed), skipping Scheduler Extended setup"); diff --git a/src/integration-tests/PromptClient.test.ts b/src/integration-tests/PromptClient.test.ts index c9edb7ba..ca0179cc 100644 --- a/src/integration-tests/PromptClient.test.ts +++ b/src/integration-tests/PromptClient.test.ts @@ -4,16 +4,17 @@ import { PromptClient, } from "../sdk"; import type { Tag } from "../open-api"; -import { describeForOrkesV5 } from "./utils/customJestDescribe"; +import { describeForOrkesOnlyV5 } from "./utils/customJestDescribe"; import { createClientWithRetry } from "./utils/createClientWithRetry"; /** - * E2E Integration Tests for PromptClient (v5 only). + * E2E Integration Tests for PromptClient. * * Tests prompt CRUD, tag management, and (optionally) prompt testing - * against a configured LLM integration. + * against a configured LLM integration. Orkes-only: the prompt registry + * (/api/prompts) does not exist on Conductor OSS. */ -describeForOrkesV5("PromptClient", () => { +describeForOrkesOnlyV5("PromptClient", () => { jest.setTimeout(60000); const clientPromise = createClientWithRetry(); diff --git a/src/integration-tests/SchedulerClient.test.ts b/src/integration-tests/SchedulerClient.test.ts index a4214b00..25fd248a 100644 --- a/src/integration-tests/SchedulerClient.test.ts +++ b/src/integration-tests/SchedulerClient.test.ts @@ -8,11 +8,11 @@ import { MetadataClient, SchedulerClient, } from "../sdk"; -import { describeForOrkesV4 } from "./utils/customJestDescribe"; +import { describeForOssSchedulerWip } from "./utils/customJestDescribe"; import { createClientWithRetry } from "./utils/createClientWithRetry"; describe("SchedulerClient", () => { - describeForOrkesV4("SchedulerClient V4+", () => { + describeForOssSchedulerWip("SchedulerClient V4+", () => { const clientPromise = createClientWithRetry(); jest.setTimeout(60000); diff --git a/src/integration-tests/SchemaClient.test.ts b/src/integration-tests/SchemaClient.test.ts index 0fc6a2e8..ca238d91 100644 --- a/src/integration-tests/SchemaClient.test.ts +++ b/src/integration-tests/SchemaClient.test.ts @@ -4,16 +4,16 @@ import { SchemaClient, } from "../sdk"; import { createClientWithRetry } from "./utils/createClientWithRetry"; -import { describeForOrkesV5 } from "./utils/customJestDescribe"; +import { describeForOrkesOnlyV5 } from "./utils/customJestDescribe"; /** * E2E Integration Tests for SchemaClient * * Tests schema registration, retrieval (by name, by name+version), listing, - * version creation, and deletion. Gated to v5: GET /api/schema/{name} is not - * supported on older backends (returns "Method 'GET' is not supported"). + * version creation, and deletion. Orkes-only: the schema registry + * (/api/schema) does not exist on Conductor OSS (returns "No static resource"). */ -describeForOrkesV5("SchemaClient", () => { +describeForOrkesOnlyV5("SchemaClient", () => { jest.setTimeout(60000); const clientPromise = createClientWithRetry(); diff --git a/src/integration-tests/SecretClient.test.ts b/src/integration-tests/SecretClient.test.ts index 4da62816..5a8a7ca3 100644 --- a/src/integration-tests/SecretClient.test.ts +++ b/src/integration-tests/SecretClient.test.ts @@ -5,7 +5,7 @@ import { } from "../sdk"; import { createClientWithRetry } from "./utils/createClientWithRetry"; import type { Tag } from "../open-api"; -import { describeForOrkesV4 } from "./utils/customJestDescribe"; +import { describeForOrkesOnlyV4 } from "./utils/customJestDescribe"; /** * E2E Integration Tests for SecretClient @@ -13,7 +13,7 @@ import { describeForOrkesV4 } from "./utils/customJestDescribe"; * Tests secret CRUD operations, existence checks, listing, and tag management. */ describe("SecretClient", () => { - describeForOrkesV4("SecretClient V4+", () => { + describeForOrkesOnlyV4("SecretClient V4+", () => { jest.setTimeout(60000); const clientPromise = createClientWithRetry(); diff --git a/src/integration-tests/ServiceRegistryClient.test.ts b/src/integration-tests/ServiceRegistryClient.test.ts index 465aa456..9654edaf 100644 --- a/src/integration-tests/ServiceRegistryClient.test.ts +++ b/src/integration-tests/ServiceRegistryClient.test.ts @@ -4,7 +4,7 @@ import { createClientWithRetry } from "./utils/createClientWithRetry"; import { ServiceType } from "../open-api"; import * as fs from "fs"; import * as path from "path"; -import { describeForOrkesV5 } from "./utils/customJestDescribe"; +import { describeForOrkesOnlyV5 } from "./utils/customJestDescribe"; import { HTTPBIN_BASE_URL } from "./utils/testConstants"; // Conductor must be able to fetch this URL for discovery; use CONDUCTOR_TEST_SERVICE_URI @@ -13,7 +13,7 @@ const TEST_SERVICE_URI = process.env.CONDUCTOR_TEST_SERVICE_URI ?? `${HTTPBIN_BASE_URL}/api-docs`; -describeForOrkesV5("ServiceRegistryClient", () => { +describeForOrkesOnlyV5("ServiceRegistryClient", () => { const clientPromise = createClientWithRetry(); let serviceRegistryClient: ServiceRegistryClient; diff --git a/src/integration-tests/TaskClient.complete.test.ts b/src/integration-tests/TaskClient.complete.test.ts index a1bc26e7..d68f5761 100644 --- a/src/integration-tests/TaskClient.complete.test.ts +++ b/src/integration-tests/TaskClient.complete.test.ts @@ -18,7 +18,7 @@ import { } from "../sdk"; import { createClientWithRetry } from "./utils/createClientWithRetry"; import { waitForWorkflowStatus } from "./utils/waitForWorkflowStatus"; -import { describeForOrkesV4 } from "./utils/customJestDescribe"; +import { describeForOrkesOnlyV4 } from "./utils/customJestDescribe"; import { registerWorkflowWithRetry } from "./utils/registerWorkflowWithRetry"; /** @@ -111,7 +111,7 @@ describe("TaskClient Complete Coverage", () => { expect(task.workflowInstanceId).toEqual(workflowId); }); - describeForOrkesV4("search (V4+)", () => { + describeForOrkesOnlyV4("search (V4+)", () => { test("search should find tasks", async () => { const result = await taskClient.search( 0, @@ -124,7 +124,7 @@ describe("TaskClient Complete Coverage", () => { expect(result).toBeDefined(); expect(result.results).toBeDefined(); }); - }); // end describeForOrkesV4 + }); // end describeForOrkesOnlyV4 test("getQueueSizeForTask should return queue sizes", async () => { const sizes = await taskClient.getQueueSizeForTask(["WAIT"]); @@ -243,7 +243,7 @@ describe("TaskClient Complete Coverage", () => { } }); - describeForOrkesV4("updateTaskResult error behavior (V4+)", () => { + describeForOrkesOnlyV4("updateTaskResult error behavior (V4+)", () => { test("updateTaskResult should throw for non-existent workflow", async () => { await expect( taskClient.updateTaskResult( @@ -254,7 +254,7 @@ describe("TaskClient Complete Coverage", () => { ) ).rejects.toThrow(); }); - }); // end describeForOrkesV4 + }); // end describeForOrkesOnlyV4 test("updateTaskSync should throw for non-existent workflow", async () => { await expect( diff --git a/src/integration-tests/WorkflowExecutor.complete.test.ts b/src/integration-tests/WorkflowExecutor.complete.test.ts index f4ee4f03..cd71bea5 100644 --- a/src/integration-tests/WorkflowExecutor.complete.test.ts +++ b/src/integration-tests/WorkflowExecutor.complete.test.ts @@ -24,7 +24,11 @@ import { waitForWorkflowStatus } from "./utils/waitForWorkflowStatus"; import { createClientWithRetry } from "./utils/createClientWithRetry"; import { registerWorkflowWithRetry } from "./utils/registerWorkflowWithRetry"; import { startWorkflowWithRetry } from "./utils/startWorkflowWithRetry"; -import { describeForOrkesV4, describeForOrkesV5 } from "./utils/customJestDescribe"; +import { + describeForOrkesV5, + describeForOrkesOnlyV4, + describeForOrkesOnlyV5, +} from "./utils/customJestDescribe"; import { registerTaskWithRetry } from "./utils/registerTaskWithRetry"; /** @@ -227,7 +231,7 @@ describe("WorkflowExecutor Complete Coverage", () => { expect(execution.tasks.length).toBeGreaterThan(0); }); - describeForOrkesV4("getWorkflowStatus (V4+)", () => { + describeForOrkesOnlyV4("getWorkflowStatus (V4+)", () => { test("getWorkflowStatus should return status summary", async () => { const status = await executor.getWorkflowStatus( workflowId, @@ -238,7 +242,7 @@ describe("WorkflowExecutor Complete Coverage", () => { expect(status).toBeDefined(); expect(status.status).toEqual("COMPLETED"); }); - }); // end describeForOrkesV4 + }); // end describeForOrkesOnlyV4 test("search should find the workflow", async () => { const result = await executor.search( @@ -255,7 +259,7 @@ describe("WorkflowExecutor Complete Coverage", () => { // ==================== Correlation IDs ==================== - describeForOrkesV4("Correlation IDs", () => { + describeForOrkesOnlyV4("Correlation IDs", () => { test("getByCorrelationIds should return workflows by correlation", async () => { const correlationId = `corr-test-${suffix}-${Date.now()}`; @@ -282,7 +286,7 @@ describe("WorkflowExecutor Complete Coverage", () => { // ==================== Workflow Control ==================== - describeForOrkesV4("Workflow Control (pause/resume/terminate/restart/retry)", () => { + describeForOrkesOnlyV4("Workflow Control (pause/resume/terminate/restart/retry)", () => { test("pause and resume should work on a running workflow", async () => { const wfId = await executor.startWorkflow({ name: waitWfName, @@ -601,7 +605,7 @@ describe("WorkflowExecutor Complete Coverage", () => { // ==================== Update Variables ==================== - describeForOrkesV4("Update Variables", () => { + describeForOrkesOnlyV4("Update Variables", () => { test("updateVariables should update workflow variables", async () => { const wfId = await executor.startWorkflow({ name: waitWfName, @@ -625,7 +629,7 @@ describe("WorkflowExecutor Complete Coverage", () => { // ==================== Update State ==================== - describeForOrkesV5("Update State", () => { + describeForOrkesOnlyV5("Update State", () => { test("updateState should update workflow and task state", async () => { const wfId = await executor.startWorkflow({ name: waitWfName, @@ -713,7 +717,7 @@ describe("WorkflowExecutor Complete Coverage", () => { // ==================== Signal Async ==================== - describeForOrkesV5("Signal Async", () => { + describeForOrkesOnlyV5("Signal Async", () => { test("signalAsync should signal a workflow asynchronously", async () => { const wfId = await startWorkflowWithRetry(executor, { name: waitWfName, diff --git a/src/integration-tests/WorkflowExecutor.test.ts b/src/integration-tests/WorkflowExecutor.test.ts index 5ea2fc52..482a2835 100644 --- a/src/integration-tests/WorkflowExecutor.test.ts +++ b/src/integration-tests/WorkflowExecutor.test.ts @@ -30,7 +30,11 @@ import { getComplexSignalTestWfDef } from "./metadata/complex_wf_signal_test"; import { getComplexSignalTestSubWf1Def } from "./metadata/complex_wf_signal_test_subworkflow_1"; import { getComplexSignalTestSubWf2Def } from "./metadata/complex_wf_signal_test_subworkflow_2"; import { getWaitSignalTestWfDef } from "./metadata/wait_signal_test"; -import { describeForOrkesV4, describeForOrkesV5 } from "./utils/customJestDescribe"; +import { + describeForOrkesV5, + describeForOrkesOnlyV4, + describeForOrkesOnlyV5, +} from "./utils/customJestDescribe"; import { registerWorkflowDefWithRetry, registerWorkflowWithRetry } from "./utils/registerWorkflowWithRetry"; import { HTTPBIN_BASE_URL } from "./utils/testConstants"; @@ -122,7 +126,7 @@ describe("WorkflowExecutor", () => { expect(workflowRun.status).toEqual("COMPLETED"); }); - describeForOrkesV4("V4+ features", () => { + describeForOrkesOnlyV4("V4+ features", () => { test("Should be able to get workflow execution status ", async () => { const client = await clientPromise; const executor = new WorkflowExecutor(client); @@ -155,7 +159,7 @@ describe("WorkflowExecutor", () => { const executionDetails = await executor.getWorkflow(executionId, true); expect(executionDetails?.idempotencyKey).toEqual(idempotencyKey); }); - }); // end describeForOrkesV4 + }); // end describeForOrkesOnlyV4 test("Should return workflow status detail", async () => { const client = await clientPromise; @@ -322,7 +326,7 @@ describe("WorkflowExecutor", () => { }); }); - describeForOrkesV5("Execute with Return Strategy and Consistency", () => { + describeForOrkesOnlyV5("Execute with Return Strategy and Consistency", () => { // Constants specific to this test suite const now = Date.now(); const WORKFLOWS = { diff --git a/src/integration-tests/utils/customJestDescribe.ts b/src/integration-tests/utils/customJestDescribe.ts index ddd01f9e..0cf353fc 100644 --- a/src/integration-tests/utils/customJestDescribe.ts +++ b/src/integration-tests/utils/customJestDescribe.ts @@ -1,6 +1,33 @@ import { describe } from "@jest/globals"; const orkesBackendVersion = Number(process.env.ORKES_BACKEND_VERSION); +const isOss = (process.env.CONDUCTOR_SERVER_TYPE || "").toLowerCase() === "oss"; + +// Diagnostic switch: set CONDUCTOR_OSS_UNGATE=1 to force the OSS-only gates +// (describeForOrkesOnly*) open even when CONDUCTOR_SERVER_TYPE=oss. Use this to +// re-probe a Conductor OSS build and see which currently-gated suites now pass — +// e.g. after an OSS release adds an endpoint — as a signal that a gate could be +// relaxed. Must NOT be set in CI: it makes Orkes-only failures look like real OSS +// failures and defeats the point of the OSS job. +const ungateOss = ["1", "true"].includes( + (process.env.CONDUCTOR_OSS_UNGATE || "").toLowerCase() +); + +// OSS gating is active only when talking to an OSS server and not explicitly ungated. +const ossGated = isOss && !ungateOss; export const describeForOrkesV5 = orkesBackendVersion >= 5 ? describe : describe.skip; export const describeForOrkesV4 = orkesBackendVersion >= 4 ? describe : describe.skip; + +// Orkes-only features: skipped when running against a Conductor OSS server. +// These still honor version gating so they only run on a compatible Orkes backend. +export const describeForOrkesOnly = !ossGated ? describe : describe.skip; +export const describeForOrkesOnlyV4 = + !ossGated && orkesBackendVersion >= 4 ? describe : describe.skip; +export const describeForOrkesOnlyV5 = + !ossGated && orkesBackendVersion >= 5 ? describe : describe.skip; + +// Scheduler exists on OSS but is a work in progress against the OSS test env, so +// skip it there for now +export const describeForOssSchedulerWip = + !ossGated && orkesBackendVersion >= 4 ? describe : describe.skip;