run integration(e2e) tests against oss server for PRs - #26
Open
chrishagglund-ship-it wants to merge 11 commits into
Open
run integration(e2e) tests against oss server for PRs#26chrishagglund-ship-it wants to merge 11 commits into
chrishagglund-ship-it wants to merge 11 commits into
Conversation
Follow-up to the OSS/cloud e2e work, from pre-review feedback. Test coverage: - Add spec/conductor/http/api/scheduler_resource_api_spec.rb, pinning the PUT-with-GET-fallback contract that the rest of this branch relies on: PUT first, 405 falls back to GET on the same path, any other status propagates untouched, no dialect memoization, and the admin/bulk endpoints staying GET. The fallback branch was previously exercised by no job that runs on a PR -- the OSS job takes the PUT path and the cloud job is skipped on pull_request. Mirrors the equivalent guards in python-sdk, go-sdk, rust-sdk and csharp-sdk. - Workflow and task search asserted only `not_to be_nil`. Both server families answer 200-with-zero-rows for a query they parse but cannot match, so the switch to the portable `field = "value"` syntax was unverifiable by its own tests. Both examples now poll until rows appear and assert the rows actually match the expected workflowType/taskType. The task-search group grew a self-contained before hook -- specs run in random order, so it cannot borrow a task from another group -- which also polls the task out of SCHEDULED, since task indexing is driven by task updates on both families. - event_spec's queue-config example had 404 added to its skip condition. OSS registers no queue/config route at all, so gate on the server type instead: a 404 from get_queue_config after a successful put is the regression this example exists to catch on Orkes, and must keep failing there. Bugfixes: - Conductor::Configuration::AuthenticationSettings has never resolved; the class is defined directly under Conductor. Fixed in RactorTaskRunner's in-Ractor config rebuild, where it was a live NameError, and in the Conductor and OrkesClients doc comments, which told users to write the broken form. Local runner and CI parity with the other SDKs: - run-integration-oss.sh now unsets CONDUCTOR_AUTH_KEY/CONDUCTOR_AUTH_SECRET. Plain OSS has no auth layer and no /token endpoint, and IntegrationHelper.configuration builds AuthenticationSettings whenever both are present -- so a shell still holding Orkes creds sent the whole local run through an auth flow the local server cannot serve. - Pull the server image unconditionally: `compose up` only pulls when an image is missing, so a cached mutable `latest` was silently reused. - Raise the CI health wait from 120s to 180s, matching HEALTH_TIMEOUT in the script and staying under the compose healthcheck's own ~200s budget. Cleanups: - Hoist the four copies of `oss?` into IntegrationHelper.oss?, and document CONDUCTOR_SERVER_TYPE alongside the other integration env vars. - Use test@conductoross.io as the fixture ownerEmail throughout. - Record the scheduler and AuthenticationSettings fixes in the changelog. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three follow-ups from pre-review, all in CI or HTTP plumbing rather than the OSS/cloud e2e work itself. Retry Faraday::ConnectionFailed: faraday-retry's DEFAULT_EXCEPTIONS is [Errno::ETIMEDOUT, 'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse] -- no ConnectionFailed, which is what the net_http_persistent adapter raises for Errno::ECONNRESET. A write to a pooled socket the peer had already closed was therefore never retried and surfaced as ApiError(status: 0). net-http-persistent retries stale sockets itself, but only for idempotent requests, so every GET in the suite was silently protected and POSTs were not -- which is how this failed the cloud integration job on run 34265187276 attempt 1, in metadata_client.register_task_def. Passing `exceptions:` explicitly is the whole fix. spec/conductor/http/rest_client_spec.rb pins it, including a behavioral example asserting the request is attempted 4 times rather than 1. CI concurrency: Supersede an in-flight run when a new commit lands on the same ref, matching python-sdk and go-sdk. More than a runner-time saving here: the cloud job's scheduler_spec calls pause_all_schedules/resume_all_schedules, which are not scoped to a test_id, so two runs of one branch overlapping will fight each other. The group is ref-scoped, so concurrent main and develop pushes are still not serialized against the shared tenant. Fork PRs: GitHub withholds org/repo variables from pull_request runs on forks exactly as it withholds secrets, so vars.E2E_TEST_OSS_CONDUCTOR_VERSION resolves empty for an outside contributor. The hard-fail guard would then red every community PR in the job this branch exists to add (observed in csharp-sdk#178). That PR drops the guard in favor of a pinned literal; this keeps the two empty cases distinct instead -- a fork PR pins a version and keeps running, while a missing org variable on a first-party run still fails loudly rather than silently drifting onto the pin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The group's `before` hook registers a TaskDef and a WorkflowDef, and the `after` hook only terminated the workflow execution. `test_id` is a `let`, so each example gets a fresh pair of uniquely-named definitions -- which means every run left two more orphans per example on the shared cloud tenant, accumulating indefinitely. Cleanup goes through this group's own `metadata_client` rather than IntegrationHelper's. The two resolve CONDUCTOR_SERVER_URL to different defaults when it is unset (localhost:7001 vs developer.orkescloud.com), so using the helper here could silently unregister against a different server than the one the `before` hook registered against. CI always sets the variable, but the local suite does not have to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Runs the integration (e2e) spec suite against both Conductor server families in CI: a Conductor OSS stack spun up in-job, and the Orkes cloud dev server.
Why
The suite only ever ran against Orkes cloud, on pushes to
main, withcontinue-on-error: true. That combination meant it protected nothing: failures were green, and two examples had in fact never passed at all (see Bugs this surfaced). This PR gets the suite running against OSS on every PR, and fixes the cloud half so that it actually gates.What changed
1. New
integration-tests-ossCI jobSpins up Conductor OSS + Postgres via
scripts/docker-compose-oss.yaml, waits for/health, and runs the full spec suite against it unauthenticated. Needs no secrets, so it runs on every push and every PR, and is notcontinue-on-error.The image tag comes from the
E2E_TEST_OSS_CONDUCTOR_VERSIONorg variable, with aworkflow_dispatchinput to override it. The job hard-fails if neither resolves — that's deliberate: if someone unsets the org variable, all SDK CI should break loudly rather than silently test nothing. (Fork PRs are the one exception, since they cannot read the variable at all — see section 7.)scripts/run-integration-oss.shruns the identical stack locally (--keep-up,--version <tag>,-- <rspec args>). The compose file and script mirror the equivalents in python-sdk, go-sdk, java-sdk, javascript-sdk, csharp-sdk and rust-sdk.2. Fixed the existing cloud
integration-testjobThis job was in scope for the ticket and was already supposed to be working. It wasn't:
continue-on-error: true. The suite now has to pass. This is the point of having it.push+refs/heads/maintopush, sodevelopis covered too.CONDUCTOR_SERVER_URLandCONDUCTOR_AUTH_KEYfromsecretstovars. Neither is a secret — one is a hostname, the other is a key identifier. Treating them as secrets bought nothing and made them unreadable and awkward to manage.CONDUCTOR_AUTH_SECRETremains a secret, because it is one.3. Bugfix: scheduler pause/resume now works on both server families
SchedulerResourceApi#pause_schedule/#resume_schedulesentGETunconditionally, which fails outright against OSS. The two families map these two per-schedule routes differently:pause/resumePUTonly (@PutMapping)GETandPUTsince the dual@RequestMapping(method = {GET, PUT})added 2026-07;GETonly before thatThere is no single verb that works everywhere, so the SDKs settled on
PUTfirst, falling back toGETon a405— and only on a405; any other status propagates untouched. This matches python-sdk, go-sdk, javascript-sdk, csharp-sdk and rust-sdk.This is a bugfix, not a breaking change. The shipped
GET-only behavior is broken against OSS, and every Orkes server that acceptedGETalso acceptsPUT.pause_all_schedules/resume_all_schedulesare untouched — both families map those admin endpointsGET.New
spec/conductor/http/api/scheduler_resource_api_spec.rb(10 examples) pins the whole contract: PUT-first, 405→GET on the same path, non-405 propagates without a fallback, no dialect memoization (every call re-attempts PUT), and the admin endpoints staying GET.4. Bugs this surfaced
Conductor::Configuration::AuthenticationSettingsraisesNameError. The class is defined directly underConductor; that nested path has never resolved.integration_helper.rbused it, which broke the three spec files that route throughIntegrationHelper.configuration—metadata_spec,workflow_spec,worker_e2e_spec— invisible undercontinue-on-error. The other seven build their ownConductor::Configuration, whose constructor already resolves auth from the same two env vars, so they were unaffected. Also fixed inRactorTaskRunner's in-Ractor config rebuild (a live failure) and in theConductor/OrkesClientsdoc comments, which were telling users to write the broken form.Conductor::Workflow::ConductorWorkflow,SimpleTaskandSetVariableTaskwere removed in the DSL redesign, soorkes_spec.rbandworker_e2e_spec.rbwere raisingNameError. Ported to theConductor.workflowDSL.5. OSS capability gating
Specs covering APIs that OSS genuinely does not implement now skip themselves via
IntegrationHelper.oss?(driven byCONDUCTOR_SERVER_TYPE, documented in that file). Each skip message records the empirically-confirmed gap: Authorization/RBAC, Schema registry, Integration Hub, Prompt templates, schedule tags, secret writes,update_workflow_state, and queue config. Nothing is skipped on the cloud side.Where behavior only differs rather than being absent, the specs assert the difference instead of skipping — e.g. secrets: reads are verified against an env-seeded secret and writes are asserted to return
501from OSS's read-onlySecretsDAO.6. Portable search query syntax
Workflow and task search used Lucene
field:valuesyntax, which OSS's default Postgres-backed indexing cannot parse. Switched to the portablefield = "value"form, which both families accept.These two examples previously only asserted
expect(results).not_to be_nil— a query the server parses but cannot match returns200with zero rows, so a syntax regression was invisible. They now poll until rows appear and assert the rows actually match the expectedworkflowType/taskType, and the task-search group creates and polls its own task so it doesn't depend on spec ordering.7. Follow-ups from pre-review
RestClientnow retriesFaraday::ConnectionFailed. faraday-retry'sDEFAULT_EXCEPTIONSomits it, and it is what thenet_http_persistentadapter raises forErrno::ECONNRESET— a write to a pooled socket the peer closed first.net-http-persistentretries stale sockets itself, but only for idempotent requests, so everyGETwas silently protected whilePOSTs surfaced as a hardApiError(status: 0). That is what failed this branch's own cloud run (34265187276 attempt 1, inregister_task_def). This does mean aPOSTis now retried on a connection failure. The existing config already retriedPOSTon500/502/503/504, where the server definitely received the request; a reset-on-write means it never did, so this is the safer of the two.spec/conductor/http/rest_client_spec.rbpins it, including an example asserting the request is attempted 4 times rather than 1.concurrency, superseding an in-flight run when a new commit lands on the same ref, matching python-sdk and go-sdk. More than a runner-time saving here: the cloud job mutates shared state on the dev tenant (scheduler_speccallspause_all_schedules/resume_all_schedules, which are not scoped to atest_id), so two runs of one branch overlapping will fight each other. The group is ref-scoped, so concurrentmainanddeveloppushes are still not serialized.pull_requestruns on forks exactly as it withholds secrets, soE2E_TEST_OSS_CONDUCTOR_VERSIONresolves empty for an outside contributor. A fork PR now pins a version and keeps running; a missing variable on a first-party run still fails loudly, per the note in section 1. (csharp-sdk#178 drops that guard entirely in favor of a pinned literal — the split is deliberate here.)Task Searchgroup now unregisters what it registers. Itsbeforehook creates aTaskDefand aWorkflowDefper example and previously only terminated the execution, orphaning both definitions on the shared tenant on every run.Test results
Before merge
e2e-against-conductor-with-local-scriptfromon.push.branchesin.github/workflows/ci.yml(kept only so this branch's pushes exercise the cloud job pre-merge; marked with a TODO).