Skip to content

version bump - #721

Merged
lmajano merged 61 commits into
masterfrom
development
Oct 13, 2026
Merged

lmajano merged 61 commits into
masterfrom
development

Conversation

@lmajano

@lmajano lmajano commented Oct 13, 2026

Copy link
Copy Markdown
Member

version bump

Github Actions and others added 30 commits September 23, 2026 15:13
RestHandler.onEntityNotFoundException() throws MissingArgumentException on Adobe ColdFusion 2023

Resolve the incoming id into a local variable before calling setData(),
since ACF 2023 drops the inline Elvis expression and the required data
argument ends up missing. Adds a regression spec asserting the id is
returned in the 404 response data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lg4ySgho8Woxb5SQN9eqep
COLDBOX-1453 RestHandler.onEntityNotFoundException() fails on Adobe ColdFusion 2023
… and its async announce thread

InterceptorService.getLazyBuffer()'s per-request buffer pool only
skipped pooling for the announce() call that itself spawns an async/
asyncAll thread. It missed that code running *inside* that spawned
thread can make its own synchronous announce() calls - most commonly
WireBox's afterInstanceAutowire announcement, fired by every
getInstance() call - which still pool against the same request-scoped
array as the original request thread.

`request` scope, and thus the pool array, is shared between a request
and any cfthread spawned from it. CFML/BoxLang arrays aren't
thread-safe, so two real concurrent threads (the request thread and an
async announce()'s thread) popping/releasing the same array can
corrupt it, throwing "can not pop Element from array, array is empty"
mid-request.

Also check controller.getUtil().inThread() (the same check
InterceptorState.process() already uses for its own async dispatch
decision) so a synchronous announce() executing inside any cfthread
always gets a fresh, unpooled buffer - never touching the array the
request thread is using.

Added regression coverage for both the pooled (reused) and
never-pooled (inside a thread / not asked to pool) paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KiK6DMek9iJMuYk2PzcjPj
An async announce()'s thread can itself become unbound from the
request that spawned it - a fire-and-forget async announce, or one
nobody joined, can keep running after the request has already ended.
By the time that orphaned thread's own work touches the buffer pool,
`request` scope may no longer be the one the pool was built against,
or may not be usable at all.

The pool is a performance nicety, never load-bearing for correctness,
so getLazyBuffer()/releaseLazyBuffer() now treat any failure reading
or writing it the same as "no pool available": fall back to a fresh,
unpooled buffer (get) or silently drop it (release), rather than
letting that propagate out of announce().

Added regression tests for both directions, and hardened the test
file's setup() to clear any leftover pool state between tests, since
request scope - and thus the pool - persists across every test in a
TestBox run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KiK6DMek9iJMuYk2PzcjPj
… in tests

CI showed 16 errors on lucee@5/6, all in InterceptorserviceTest.cfc:
mockController.$( "getUtil", ... ) made MockBox generate a stub method
whose inferred return type was "string", then fail to cast the real
Util component (or, in one test, a TestBox Stub) to it.

Switched to mockController.setUtil( ... ), a real accessor-generated
setter rather than a MockBox $() stub - the same pattern setLogBox()/
setWireBox()/setCacheBox() already use for their own properties in
this same setup(), and which doesn't go through MockBox's return-type
inference at all.

Lucee 5/6 aren't available in this sandbox (no cached artifact, and
ForgeBox is unreachable here) to reproduce directly, but this removes
the exact code path CI's stack trace pointed at, in favor of a pattern
already proven safe on every engine in this same file. Re-verified
27/27 passing on Lucee 7 across repeated runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KiK6DMek9iJMuYk2PzcjPj
COLDBOX-1454: fix interceptor buffer pool race between a request and its async announce thread
event.route( "name@module" ) joined the module inherited entry point and the
route pattern without a separator, so "login@BrowserTesting" built
"__browser-testinglogin/3/". ModuleService stores inherited entry points
without a trailing slash, so the link now joins them with a single slash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
…dpoints

GET /__browser-testing/login/:id and GET /__browser-testing/logout call the
login and logout closures of moduleSettings.browserTesting. Every request
gets a plain 404 unless the environment is testing, the module is enabled,
a token is configured and matches the X-Browser-Testing-Token header or the
token variable (compared in constant time), and the closure is set. The
test harness enables it, and runs in its testing environment on 127.0.0.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
coldbox.system.testing.BrowserTestCase (BoxLang) extends BaseTestCase and
delegates to TestBox's BrowserSupport like BrowserSpec: browse(),
this.playwright(), browserAvailable(), the browserProfile and baseURL
annotations, the browser matchers and the afterAll closeBrowser() hook.
It adds routeURL(), visitRoute() and assertRouteIs() for named routes, and
loginAs() and logout() through the BrowserTesting core module.

The specs live in tests/specs/browser, which tests/runner.cfm skips on
engines other than BoxLang; the browser specs skip without bx-playwright.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
…corator mocks on Adobe

BrowserTestCase registered testbox.system.browser.BrowserMatchers in its
pseudo constructor, so every runner.cfm request on BoxLang failed with a 500
on released TestBox builds that do not ship testbox.system.browser yet. It
now detects TestBox browser support, registers the matchers only when present,
and skips browser specs with a clear reason otherwise. The route helpers still
run.

RequestContext.getMemento() returned the `this` reference that Adobe keeps in
the variables scope. RequestContextDecorator restored it, so on Adobe the
decorator's inherited methods ran with `this` pointing to the original context
and mocked methods failed with "Element _MOCKRESULTS is undefined in THIS"
(BrowserTestingModuleSpec header token spec). The memento now leaves it out,
with regression specs in RequestContextDecoratorTest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
The test-only login endpoints were a backdoor. Logged-in browser tests now
use bx-playwright saved sessions: log in through the login page once with
this.playwright().session(), then browse( ..., { session : "name" } ).

The test harness gets a plain login form for the saved session specs, and
goes back to its development environment on 127.0.0.1. Module route specs
use the resourcesTest module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
- visitRoute() no longer skips the spec without browser support: it only
  visits the page it gets, and pages come from browse(), which skips.
- assertRouteIs() matches every route registered with the name, so a route
  with optional placeholders (/posts/:id?, registered as /posts/:id and
  /posts) matches with and without them. Module routes use the module
  routing table.
- The harness login form renders the logged-in page instead of relocating:
  the harness runs with the test controller, which intercepts relocations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5 to 6.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](actions/setup-java@v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [EndBug/add-and-commit](https://github.com/endbug/add-and-commit) from 10.0.0 to 11.1.1.
- [Release notes](https://github.com/endbug/add-and-commit/releases)
- [Commits](EndBug/add-and-commit@v10.0.0...v11.1.1)

---
updated-dependencies:
- dependency-name: EndBug/add-and-commit
  dependency-version: 11.1.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [ortus-boxlang/setup-boxlang](https://github.com/ortus-boxlang/setup-boxlang) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/ortus-boxlang/setup-boxlang/releases)
- [Changelog](https://github.com/ortus-boxlang/setup-boxlang/blob/development/changelog.md)
- [Commits](ortus-boxlang/setup-boxlang@1.4.0...1.5.0)

---
updated-dependencies:
- dependency-name: ortus-boxlang/setup-boxlang
  dependency-version: 1.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [mikepenz/action-junit-report](https://github.com/mikepenz/action-junit-report) from 6.4.2 to 6.5.0.
- [Release notes](https://github.com/mikepenz/action-junit-report/releases)
- [Commits](mikepenz/action-junit-report@v6.4.2...v6.5.0)

---
updated-dependencies:
- dependency-name: mikepenz/action-junit-report
  dependency-version: 6.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
…ting to the scheduler's restart time

syncScheduleWithCluster() unconditionally realigned a server-fixation
task's initial delay to the cluster lock's scheduleStart anchor
whenever a lock already existed, with no regard for whether the task
already had its own deterministic, wall-clock-computed delay.

That anchor is getScheduler().getStartedAt() - the scheduler's own
startup timestamp - which has no relationship to a calendar task's
configured time of day. Calendar DSL methods (everyDayAt(),
everyHourAt(), everyWeekOn(), everyMonthOn(), everyYearOn(),
onFirstBusinessDayOfTheMonth(), onLastBusinessDayOfTheMonth()) already
compute their own initial delay from the real clock at DSL-call time,
independently and identically on every server. syncScheduleWithCluster()
was stomping on that correct delay and replacing it with one aligned to
the scheduler's last restart time instead - so a task declared
everyDayAt("04:00") would, after any restart that left a prior cluster
lock in place, start firing every day at whatever time-of-day the
scheduler happened to (re)start at, and that time-of-day compounds on
every subsequent restart.

Plain every(n, unit) tasks have no inherent wall-clock anchor (every
server starts at a different moment), so they still need this sync to
avoid drifting relative to each other - that behavior is preserved.
The fix skips the sync only when the task already has a positive,
deterministically-computed initial delay (getDelay() > 0), which is
exactly and only true for the calendar DSL methods.

Added regression coverage for everyDayAt() staying put across a sync
with a stale/wrong anchor, while confirming plain every() still syncs
as before.

Verified directly against a live BoxLang 1.17.5 engine (standalone,
bypassing this sandbox's MySQL-dependent integration test harness):
reverting the fix and re-running reproduced the exact reported
drift (an everyDayAt("04:00") task's delay changing from the correct
~16.5h to a bogus ~15h matching a simulated stale 9-hours-ago anchor);
restoring the fix leaves the delay untouched across the same scenario,
while a plain every(2,"hours") task is still realigned as designed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KiK6DMek9iJMuYk2PzcjPj
Fix onOneServer() + calendar tasks drifting to the scheduler's restart time after a restart
…s/setup-java-6

chore(deps): bump actions/setup-java from 5 to 6
…nz/action-junit-report-6.5.0

chore(deps): bump mikepenz/action-junit-report from 6.4.2 to 6.5.0
…boxlang/setup-boxlang-1.5.0

chore(deps): bump ortus-boxlang/setup-boxlang from 1.4.0 to 1.5.0
…/add-and-commit-11.1.1

chore(deps): bump EndBug/add-and-commit from 10.0.0 to 11.1.1
execute(), and the get()/post()/etc helpers built on it, ran the global
preProcess/postProcess interceptors but never Router.middleware() targets,
so a route protected by middleware looked unprotected in integration tests.

Run them in the same order as the Bootstrap: route middleware right after the
global preProcess announce, and before the global postProcess announce.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E8yy4oK5vnaVucmYLg4UtY
group() already shared middleware, but a `meta` option was silently ignored:
the withClosure merge skips complex values that the route already declares,
and addRoute() always declares `meta`. Inherit it through its own stack, like
the group middleware, so one group can declare route metadata once.

Keys merge shallowly, outer group first, and the route's own meta() wins.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E8yy4oK5vnaVucmYLg4UtY
claude and others added 29 commits October 6, 2026 09:37
…hreads

Singleton, engine (application, session, server) and CacheBox scopes store an
object before wiring so circular dependencies resolve, but other threads read it
outside the lock and got a half-wired instance.

Each scope now tracks keys currently wiring. A thread seeing such a key queues
on one shared per-injector lock. The wiring thread re-enters its own lock and
gets the stored instance, so circular dependencies still resolve. One lock for
all scopes makes lock-ordering deadlocks impossible.

Also renames CFScopes to EngineScopes. Supersedes #706.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YodhiJEvwNCVBSDJEnRTZ
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YodhiJEvwNCVBSDJEnRTZ
A statement starting with an array literal does not parse on Adobe 2023.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YodhiJEvwNCVBSDJEnRTZ
COLDBOX-1455 WireBox scopes: do not hand an unwired object to other threads
…initiated

During a reinit loadColdBox() puts a new, not yet initiated controller in
application scope. onSessionStart() only checked that the key existed, so a
new session in that window ran the session start handler against a half-loaded
controller and returned a 500 instead of the fail-fast 503.

Check getColdboxInitiated() as well, the same readiness test reloadChecks()
and onSessionEnd() use. A not-ready controller is skipped, the same as the
existing missing-key case. Supersedes #707.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YodhiJEvwNCVBSDJEnRTZ
COLDBOX-1456 Bootstrap: skip a session start until the controller is initiated
…scope build completes

A singleton, engine scope or CacheBox object built inside another build left
the wiring set when its own wiring ended, while holding a reference to the
outer object that was still wiring. Other threads then took the lock-free path
and reached the half-wired outer object.

The injector now owns the set of keys in flight and a build depth counter,
shared by all scopes, and clears the set when the outermost build returns.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155rEwu5EVDK6HdcLfgczu7
…ning mocks are valid on Lucee

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155rEwu5EVDK6HdcLfgczu7
WireBox: hold circular partners as wiring until the outermost scope build completes
…bda and instance middleware

COLDBOX-1460. Registers a closure, lambda, object instance or WireBox ID
under a name, singly or as a struct of name : target pairs. It is sugar
over middlewareGroup(): the name is stored as a one-member bundle in the
same namespace, so middleware(), group() and withoutMiddleware() resolve it
with no new code. Duplicate names throw Router.DuplicateMiddleware unless
force is true.

Also fixes normalizeMiddlewareEntries() treating a component instance as a
struct on engines where isStruct() is true for components.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E8yy4oK5vnaVucmYLg4UtY
COLDBOX-1460 Add Router.registerMiddleware() for named closure, lambda and instance middleware
…port

When closeBrowser() never runs (an afterAll() that throws, an aborted
request), TestBox's BrowserSupport closes the browser the next time a run
opens one (Ortus-Solutions/TestBox#212).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
…Case

TestBox now attaches browser support to any spec annotated with @browser,
@browserprofile or @baseurl (Ortus-Solutions/TestBox#222), so ColdBox no
longer needs a parallel BrowserTestCase hierarchy. BaseTestCase keeps
loading the app and gains the named route helpers: routeURL(),
visitRoute() and assertRouteIs(). BrowserTestCase is removed and its spec
becomes BrowserTestingSpec, a BaseTestCase with @browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
Adobe ColdFusion cannot call a member function on a parenthesized
expression, so `( route.regexPattern ?: "" ).reReplace(...)` failed to
compile on Adobe 2023/2025. The helpers now call the reReplace() function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
…an-ddfqpm

COLDBOX-1457 Browser testing for ColdBox apps: @browser on BaseTestCase and named route helpers
…-app error

- routeURL(), visitRoute() and assertRouteIs() build paths from the web
  root: the appMapping is a mapping, not a web path, so it is no longer
  part of them. A webMapping keeps the base path for apps served from a
  sub folder.
- With params, a route with optional placeholders resolves to the
  longest variant the params fill, instead of the base route. A root
  route gives "/", not "//".
- The helpers throw BaseTestCase.NoColdBoxApp when no ColdBox app is
  loaded, and InvalidArgumentException for a module that is not loaded.
- assertRouteIs() rethrows unexpected errors as they are and reports the
  page URL as is when it is not a valid URI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
…an-ddfqpm

BaseTestCase route helpers: web root paths, optional placeholders, no-app error
Implicit Views Disabled by default for security
@lmajano
lmajano merged commit fc2b6c7 into master Oct 13, 2026
27 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants