Skip to content

Commit 6450358

Browse files
CCOR-13193 - test against oss server in ci (#129)
* adding testing against oss server in gh actions * use correct httpbin image * temporarily restrict gh action to just the stuff we're trying out * skip Orkes-only tests when running against OSS, via custom gated describe wrappers configured by env vars * try oss with all 3 versions of node * restore full ci with tests against remote v4 and v5 servers
1 parent 9347c3b commit 6450358

17 files changed

Lines changed: 316 additions & 48 deletions

.github/workflows/pull_request.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,60 @@ jobs:
197197
flags: integration-v4-sm
198198
name: codecov-integration-v4-sm-node-${{ matrix.node-version }}-shard-${{ matrix.shard }}
199199
fail_ci_if_error: false
200+
201+
# Integration tests (OSS): spins up Conductor OSS + Postgres via
202+
# scripts/docker-compose-oss.yaml and runs the integration suite unauthenticated,
203+
# with Orkes-only tests gated out via CONDUCTOR_SERVER_TYPE=oss (see
204+
# test:integration:oss). The same stack can be run locally with
205+
# scripts/run-integration-oss.sh.
206+
integration-tests-oss:
207+
runs-on: ubuntu-latest
208+
timeout-minutes: 30
209+
strategy:
210+
fail-fast: false
211+
matrix:
212+
node-version: [20, 22, 24]
213+
name: Node.js v${{ matrix.node-version }} - integration oss
214+
env:
215+
CONDUCTOR_SERVER_URL: http://localhost:8080/api
216+
CONDUCTOR_SERVER_TYPE: oss
217+
CONDUCTOR_REQUEST_TIMEOUT_MS: "120000"
218+
CONDUCTOR_RETRY_SERVER_ERRORS: "true"
219+
HTTPBIN_SERVICE_HOSTNAME: httpbin
220+
steps:
221+
- name: Checkout
222+
uses: actions/checkout@v4
223+
- name: Set up Node
224+
uses: actions/setup-node@v4
225+
with:
226+
node-version: ${{ matrix.node-version }}
227+
cache: "npm"
228+
- name: Cache node_modules
229+
id: cache
230+
uses: actions/cache@v4
231+
with:
232+
path: node_modules
233+
key: npm-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
234+
restore-keys: |
235+
npm-${{ matrix.node-version }}-
236+
- name: Install Dependencies
237+
if: steps.cache.outputs.cache-hit != 'true'
238+
run: npm ci
239+
- name: Start Conductor OSS stack
240+
run: docker compose -f scripts/docker-compose-oss.yaml up -d
241+
- name: Wait for Conductor to be healthy
242+
run: timeout 120 bash -c 'until curl -sf http://localhost:8080/health; do sleep 5; done'
243+
- name: Run integration tests (OSS)
244+
run: npm run test:integration:oss -- --ci --runInBand --testTimeout=120000 --reporters=default --reporters=github-actions --reporters=jest-junit
245+
env:
246+
JEST_JUNIT_OUTPUT_NAME: integration-oss-node-${{ matrix.node-version }}-test-results.xml
247+
- name: Dump Conductor logs
248+
if: failure()
249+
run: docker compose -f scripts/docker-compose-oss.yaml logs conductor-server
250+
- name: Publish Test Results
251+
uses: dorny/test-reporter@v2
252+
if: ${{ !cancelled() }}
253+
with:
254+
name: integration oss (Node ${{ matrix.node-version }})
255+
path: reports/integration-oss-node-${{ matrix.node-version }}-test-results.xml
256+
reporter: jest-junit

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"test:integration:base": "jest --force-exit --detectOpenHandles --testMatch='**/src/integration-tests/*.test.[jt]s?(x)'",
5252
"test:integration:v5": "cross-env ORKES_BACKEND_VERSION=5 npm run test:integration:base --",
5353
"test:integration:v4": "cross-env ORKES_BACKEND_VERSION=4 npm run test:integration:base --",
54+
"test:integration:oss": "cross-env ORKES_BACKEND_VERSION=5 CONDUCTOR_SERVER_TYPE=oss npm run test:integration:base --",
5455
"test:integration:v5:batch": "cross-env ORKES_BACKEND_VERSION=5 node scripts/run-integration-batch.mjs",
5556
"test:integration:v4:batch": "cross-env ORKES_BACKEND_VERSION=4 node scripts/run-integration-batch.mjs",
5657
"ci": "npm run lint && npm run test",

scripts/docker-compose-oss.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Conductor OSS stack used to run the SDK integration tests against open-source
2+
# Conductor. Shared by scripts/run-integration-oss.sh and the
3+
# integration-tests-oss job in .github/workflows/pull_request.yml.
4+
#
5+
# The Conductor server reaches httpbin over the compose network at
6+
# http://httpbin:8081, which matches HTTPBIN_SERVICE_HOSTNAME=httpbin.
7+
services:
8+
conductor-server:
9+
image: conductoross/conductor:latest
10+
environment:
11+
- CONFIG_PROP=config-postgres.properties
12+
ports:
13+
- "8080:8080"
14+
healthcheck:
15+
test: ["CMD", "curl", "-I", "-XGET", "http://localhost:8080/health"]
16+
interval: 10s
17+
timeout: 10s
18+
retries: 20
19+
links:
20+
- conductor-postgres:postgresdb
21+
depends_on:
22+
conductor-postgres:
23+
condition: service_healthy
24+
conductor-postgres:
25+
image: postgres:16
26+
environment:
27+
- POSTGRES_USER=conductor
28+
- POSTGRES_PASSWORD=conductor
29+
healthcheck:
30+
test: timeout 5 bash -c 'cat < /dev/null > /dev/tcp/localhost/5432'
31+
interval: 5s
32+
timeout: 5s
33+
retries: 12
34+
httpbin:
35+
image: ghcr.io/orkes-io/test-utils/httpbin:latest
36+
expose:
37+
- "8081"

scripts/run-integration-oss.sh

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Spin up a local Conductor OSS stack and run the SDK integration suite against
4+
# it, mirroring the `integration-tests-oss` job in
5+
# .github/workflows/pull_request.yml. Orkes-only tests are gated out via
6+
# CONDUCTOR_SERVER_TYPE=oss (see the test:integration:oss npm script).
7+
#
8+
# The stack (Conductor OSS + Postgres + httpbin) is defined in
9+
# scripts/docker-compose-oss.yaml and is torn down automatically on exit.
10+
#
11+
# Test output is written to both stdout and a log file (default:
12+
# scripts/oss-test-run.log, override with -l|--log) so it can be shared later.
13+
#
14+
# Usage:
15+
# scripts/run-integration-oss.sh [-t|--test <path|pattern>] [-l|--log <file>] [--keep-up] [-- jest args]
16+
# Examples:
17+
# scripts/run-integration-oss.sh # full OSS-gated suite
18+
# scripts/run-integration-oss.sh --test WorkflowExecutor
19+
# scripts/run-integration-oss.sh --log /tmp/oss.log # custom log path
20+
# scripts/run-integration-oss.sh --keep-up # leave the stack running afterwards
21+
# scripts/run-integration-oss.sh -- --testPathPatterns="EventClient"
22+
set -euo pipefail
23+
24+
TEST_PATTERN=""
25+
KEEP_UP=0
26+
LOG_FILE=""
27+
28+
# Print the leading comment block (everything after the shebang up to the first
29+
# non-comment line) as help text, so it stays in sync with the header above.
30+
usage() { awk 'NR>1 && /^#/ {sub(/^# ?/, ""); print; next} NR>1 {exit}' "$0"; }
31+
32+
extra=()
33+
while [[ $# -gt 0 ]]; do
34+
case "$1" in
35+
-t|--test) TEST_PATTERN="${2:?--test needs a path or pattern}"; shift 2 ;;
36+
-l|--log) LOG_FILE="${2:?--log needs a file path}"; shift 2 ;;
37+
--keep-up) KEEP_UP=1; shift ;;
38+
-h|--help) usage; exit 0 ;;
39+
--) shift; extra=("$@"); break ;;
40+
*) echo "Unknown argument: $1" >&2; usage; exit 1 ;;
41+
esac
42+
done
43+
44+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
45+
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
46+
COMPOSE_FILE="${SCRIPT_DIR}/docker-compose-oss.yaml"
47+
LOG_FILE="${LOG_FILE:-${SCRIPT_DIR}/oss-test-run.log}"
48+
cd "${REPO_ROOT}"
49+
50+
# Guard: this script spins up and tests against a *local* OSS stack. If enterprise
51+
# / remote server vars are already in the environment they would silently redirect
52+
# the suite at a deployed server (e.g. sdkdev) and make Orkes-only tests pass,
53+
# defeating the purpose of the OSS run. Refuse to run unless explicitly overridden.
54+
if [[ "${ALLOW_REMOTE_SERVER:-0}" != "1" ]]; then
55+
offending=()
56+
[[ -n "${CONDUCTOR_AUTH_KEY:-}" ]] && offending+=("CONDUCTOR_AUTH_KEY")
57+
[[ -n "${CONDUCTOR_AUTH_SECRET:-}" ]] && offending+=("CONDUCTOR_AUTH_SECRET")
58+
if [[ -n "${CONDUCTOR_SERVER_URL:-}" \
59+
&& ! "${CONDUCTOR_SERVER_URL}" =~ ^https?://(localhost|127\.0\.0\.1)(:|/|$) ]]; then
60+
offending+=("CONDUCTOR_SERVER_URL=${CONDUCTOR_SERVER_URL}")
61+
fi
62+
if (( ${#offending[@]} > 0 )); then
63+
echo "Error: refusing to run the OSS suite — remote/enterprise server vars are set:" >&2
64+
printf ' - %s\n' "${offending[@]}" >&2
65+
echo >&2
66+
echo "This script runs against a LOCAL OSS stack. Unset these first:" >&2
67+
echo " unset CONDUCTOR_SERVER_URL CONDUCTOR_AUTH_KEY CONDUCTOR_AUTH_SECRET" >&2
68+
echo "Or set ALLOW_REMOTE_SERVER=1 to bypass this check intentionally." >&2
69+
exit 1
70+
fi
71+
fi
72+
73+
CONDUCTOR_SERVER_URL="${CONDUCTOR_SERVER_URL:-http://localhost:8080/api}"
74+
HEALTH_URL="${CONDUCTOR_SERVER_URL%/api}/health"
75+
76+
compose() { docker compose -f "${COMPOSE_FILE}" "$@"; }
77+
78+
cleanup() {
79+
if [[ "${KEEP_UP}" == "1" ]]; then
80+
echo "--keep-up set: leaving the OSS stack running. Tear down with:"
81+
echo " docker compose -f ${COMPOSE_FILE} down -v"
82+
return
83+
fi
84+
echo "Tearing down Conductor OSS stack..."
85+
compose down -v || true
86+
}
87+
trap cleanup EXIT
88+
89+
echo "Starting Conductor OSS stack (${COMPOSE_FILE})..."
90+
compose up -d
91+
92+
echo "Waiting for Conductor to be healthy at ${HEALTH_URL} ..."
93+
# Portable wait loop using bash's built-in SECONDS (macOS has no `timeout`).
94+
HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-180}"
95+
deadline=$(( SECONDS + HEALTH_TIMEOUT ))
96+
until curl -sf "${HEALTH_URL}" >/dev/null 2>&1; do
97+
if (( SECONDS >= deadline )); then
98+
echo "Error: Conductor did not become healthy within ${HEALTH_TIMEOUT}s." >&2
99+
compose logs conductor-server || true
100+
exit 1
101+
fi
102+
sleep 5
103+
done
104+
echo "Conductor is up."
105+
106+
export CONDUCTOR_SERVER_URL
107+
export CONDUCTOR_SERVER_TYPE=oss
108+
export HTTPBIN_SERVICE_HOSTNAME="${HTTPBIN_SERVICE_HOSTNAME:-httpbin}"
109+
export CONDUCTOR_REQUEST_TIMEOUT_MS="${CONDUCTOR_REQUEST_TIMEOUT_MS:-120000}"
110+
export CONDUCTOR_RETRY_SERVER_ERRORS="${CONDUCTOR_RETRY_SERVER_ERRORS:-true}"
111+
112+
test_cmd=(npm run test:integration:oss -- --ci --runInBand --testTimeout=120000)
113+
if [[ -n "${TEST_PATTERN}" ]]; then
114+
# Targeting a specific test: pass it straight through as a path pattern.
115+
test_cmd+=("--testPathPatterns=${TEST_PATTERN}")
116+
fi
117+
if (( ${#extra[@]} > 0 )); then
118+
test_cmd+=("${extra[@]}")
119+
fi
120+
121+
if [[ -n "${TEST_PATTERN}" ]]; then
122+
echo "Running OSS integration tests | test '${TEST_PATTERN}' | server ${CONDUCTOR_SERVER_URL}"
123+
else
124+
echo "Running OSS integration tests | full OSS-gated suite | server ${CONDUCTOR_SERVER_URL}"
125+
fi
126+
echo "Writing output to ${LOG_FILE}"
127+
128+
# Tee to a log file for later sharing. pipefail ensures the test command's exit
129+
# status (not tee's) is what propagates.
130+
"${test_cmd[@]}" 2>&1 | tee "${LOG_FILE}"

src/integration-tests/ApplicationClient.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import {
99
import { ApplicationClient } from "../sdk";
1010
import { createClientWithRetry } from "./utils/createClientWithRetry";
1111
import type { Tag } from "../open-api";
12-
import { describeForOrkesV4 } from "./utils/customJestDescribe";
12+
import { describeForOrkesOnlyV4 } from "./utils/customJestDescribe";
1313

1414
describe("ApplicationClient", () => {
15-
describeForOrkesV4("ApplicationClient V4+", () => {
15+
describeForOrkesOnlyV4("ApplicationClient V4+", () => {
1616
jest.setTimeout(60000);
1717

1818
let applicationClient: ApplicationClient;
@@ -578,5 +578,5 @@ describe("ApplicationClient", () => {
578578
// ).rejects.toThrow();
579579
// });
580580
});
581-
}); // end describeForOrkesV4
581+
}); // end describeForOrkesOnlyV4
582582
});

src/integration-tests/AuthorizationClient.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
MetadataClient,
66
} from "../sdk";
77
import { createClientWithRetry } from "./utils/createClientWithRetry";
8-
import { describeForOrkesV4 } from "./utils/customJestDescribe";
8+
import { describeForOrkesOnlyV4 } from "./utils/customJestDescribe";
99
import { registerWorkflowDefWithRetry } from "./utils/registerWorkflowWithRetry";
1010

1111
/**
@@ -15,7 +15,7 @@ import { registerWorkflowDefWithRetry } from "./utils/registerWorkflowWithRetry"
1515
* in a lifecycle order: create → read → update → delete.
1616
*/
1717
describe("AuthorizationClient", () => {
18-
describeForOrkesV4("AuthorizationClient V4+", () => {
18+
describeForOrkesOnlyV4("AuthorizationClient V4+", () => {
1919
jest.setTimeout(60000);
2020

2121
const suffix = Date.now();
@@ -355,5 +355,5 @@ describe("AuthorizationClient", () => {
355355
).rejects.toThrow();
356356
});
357357
});
358-
}); // end describeForOrkesV4
358+
}); // end describeForOrkesOnlyV4
359359
});

src/integration-tests/EventClient.test.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import type {
1414
} from "../open-api";
1515
import { EventClient } from "../sdk";
1616
import { createClientWithRetry } from "./utils/createClientWithRetry";
17-
import { describeForOrkesV4, describeForOrkesV5 } from "./utils/customJestDescribe";
17+
import {
18+
describeForOrkesOnlyV4,
19+
describeForOrkesOnlyV5,
20+
} from "./utils/customJestDescribe";
1821
import { pollUntil } from "./utils/pollUntil";
1922

2023
const TEST_HANDLER_NAME_PREFIX = "jsSdkTest:";
@@ -79,7 +82,7 @@ describe("EventClient", () => {
7982
description: `Test event handler: ${name}`,
8083
});
8184

82-
describeForOrkesV4("Event Handler Management", () => {
85+
describeForOrkesOnlyV4("Event Handler Management", () => {
8386
test("Should add a single event handler", async () => {
8487
const handlerName = createUniqueName("event-handler");
8588
const eventName = createUniqueName("event");
@@ -312,7 +315,7 @@ describe("EventClient", () => {
312315
});
313316
});
314317

315-
describeForOrkesV4("Tag Management", () => {
318+
describeForOrkesOnlyV4("Tag Management", () => {
316319
test("Should get tags for an event handler", async () => {
317320
const handlerName = createUniqueName("event-handler");
318321
const eventName = createUniqueName("event");
@@ -483,7 +486,7 @@ describe("EventClient", () => {
483486
});
484487
});
485488

486-
describeForOrkesV4("Test Endpoint", () => {
489+
describeForOrkesOnlyV4("Test Endpoint", () => {
487490
test("Should call test endpoint", async () => {
488491

489492
const result = await eventClient.test();
@@ -575,7 +578,7 @@ describe("EventClient", () => {
575578
});
576579
});
577580

578-
describeForOrkesV5("Event Processing", () => {
581+
describeForOrkesOnlyV5("Event Processing", () => {
579582
test("Should handle incoming event", async () => {
580583
const handlerName = createUniqueName("event-handler");
581584
const eventName = createUniqueName("event");
@@ -666,7 +669,7 @@ describe("EventClient", () => {
666669
});
667670
});
668671

669-
describeForOrkesV5("Event Executions and Statistics", () => {
672+
describeForOrkesOnlyV5("Event Executions and Statistics", () => {
670673
test("Should get all active event handlers (execution view)", async () => {
671674

672675
const handlerName = createUniqueName("event-handler");

src/integration-tests/MetadataClient.complete.test.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import {
1515
OrkesClients,
1616
} from "../sdk";
1717
import { createClientWithRetry } from "./utils/createClientWithRetry";
18-
import { describeForOrkesV4 } from "./utils/customJestDescribe";
18+
import {
19+
describeForOrkesOnly,
20+
describeForOrkesOnlyV4,
21+
describeForOssSchedulerWip,
22+
} from "./utils/customJestDescribe";
1923
import { registerWorkflowDefWithRetry } from "./utils/registerWorkflowWithRetry";
2024

2125
/**
@@ -168,7 +172,7 @@ describe("MetadataClient Complete Coverage", () => {
168172

169173
// ==================== Workflow Tags ====================
170174

171-
describeForOrkesV4("Workflow Tags", () => {
175+
describeForOrkesOnlyV4("Workflow Tags", () => {
172176
test("addWorkflowTag should add a tag to a workflow definition", async () => {
173177
await expect(
174178
metadataClient.addWorkflowTag(
@@ -222,7 +226,7 @@ describe("MetadataClient Complete Coverage", () => {
222226

223227
// ==================== Task Tags ====================
224228

225-
describeForOrkesV4("Task Tags", () => {
229+
describeForOrkesOnlyV4("Task Tags", () => {
226230
test("addTaskTag should add a tag to a task definition", async () => {
227231
await expect(
228232
metadataClient.addTaskTag(
@@ -276,7 +280,7 @@ describe("MetadataClient Complete Coverage", () => {
276280
// ==================== Rate Limits ====================
277281
// Rate limit API may not be available on all server versions
278282

279-
describe("Rate Limits", () => {
283+
describeForOrkesOnly("Rate Limits", () => {
280284
let rateLimitSupported = true;
281285

282286
test("setWorkflowRateLimit should configure rate limiting", async () => {
@@ -320,7 +324,7 @@ describe("MetadataClient Complete Coverage", () => {
320324

321325
// ==================== Scheduler Extended ====================
322326

323-
describeForOrkesV4("Scheduler Extended", () => {
327+
describeForOssSchedulerWip("Scheduler Extended", () => {
324328
beforeAll(async () => {
325329
if (!schedulerClient) {
326330
console.warn("schedulerClient is undefined (client creation likely failed), skipping Scheduler Extended setup");

0 commit comments

Comments
 (0)