This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new f167a9dccb Implement TODO-146: mixin access to host RestContext via 
RestMixin.getHostContext()
f167a9dccb is described below

commit f167a9dccbc7e2db46b09bc845747c4556570231
Author: James Bognar <[email protected]>
AuthorDate: Mon Jun 1 10:08:36 2026 -0400

    Implement TODO-146: mixin access to host RestContext via 
RestMixin.getHostContext()
    
    - Add RestMixin.getHostContext() — returns the host RestContext via the 
mixin sub-context's getParentContext() (captured through a new protected 
setContext(RestContext)); null when standalone, flat top-level host under 
nesting.
    - Add NavigationMixin (true-mixin navigation page) enumerating the host's 
children via getHostContext().getRestChildren().
    - Retain concrete getChildren on the Basic*Group bases (mixin sub-contexts 
don't yet inherit the host's class-level @HtmlDocConfig); Javadoc now points at 
NavigationMixin. Follow-up tracked as TODO-148.
    - Tests: RestMixin_HostContext_Test, NavigationMixin_AsMixin_Test.
    - Planning: add TODO-148 (mixin @HtmlDocConfig inheritance) + TODO.md 
registry/status updates.
---
 .../springboot/BasicSpringRestServletGroup.java    |   9 +-
 .../apache/juneau/rest/ops/NavigationMixin.java    |  86 +++++++++++++++++
 .../rest/servlet/BasicRestResourceGroup.java       |   9 +-
 .../juneau/rest/servlet/BasicRestServletGroup.java |   9 +-
 .../org/apache/juneau/rest/servlet/RestMixin.java  |  78 ++++++++++++++-
 .../rest/ops/NavigationMixin_AsMixin_Test.java     |  62 ++++++++++++
 .../rest/servlet/RestMixin_HostContext_Test.java   | 105 +++++++++++++++++++++
 juneau-utest/test-run-history.tsv                  |   1 +
 8 files changed, 352 insertions(+), 7 deletions(-)

diff --git 
a/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServletGroup.java
 
b/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServletGroup.java
index 0f05aa98b9..c1f0e8e9a5 100644
--- 
a/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServletGroup.java
+++ 
b/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServletGroup.java
@@ -20,6 +20,7 @@ import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.beans.*;
 import org.apache.juneau.rest.config.*;
+import org.apache.juneau.rest.ops.*;
 
 /**
  * Specialized subclass of {@link BasicSpringRestServlet} for showing "group" 
pages.
@@ -34,8 +35,12 @@ import org.apache.juneau.rest.config.*;
  * <p>
  * Adds the group-navigation endpoint ({@code GET /}) as a concrete method on 
top of the residual op-mixins
  * inherited from {@link BasicSpringRestServlet}. The navigation page is 
rendered as a method of the host
- * resource (rather than a sub-context mixin) so it inherits the host's
- * {@link org.apache.juneau.html.annotation.HtmlDocConfig @HtmlDocConfig} page 
decoration.
+ * resource (rather than the {@link NavigationMixin} sub-context mixin) so it 
inherits the host's
+ * {@link org.apache.juneau.html.annotation.HtmlDocConfig @HtmlDocConfig} page 
decoration &mdash; a mixin
+ * sub-context does not inherit the host's class-level {@code @HtmlDocConfig}, 
so a navigation op living on a
+ * mixin renders without the host's navlinks/aside/footer/theme.  {@link 
NavigationMixin} (backed by
+ * {@link org.apache.juneau.rest.servlet.RestMixin#getHostContext()}) is the 
mixin flavor for hosts where that
+ * page decoration is not required (e.g. JSON-only APIs).
  *
  * <p>
  * Children are attached to this resource using the {@link Rest#children() 
@Rest(children)} annotation.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/NavigationMixin.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/NavigationMixin.java
new file mode 100644
index 0000000000..df8bf1cb1b
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/NavigationMixin.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.ops;
+
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.beans.*;
+import org.apache.juneau.rest.servlet.*;
+
+/**
+ * Mixin providing the group-navigation endpoint ({@code [GET /]}) that lists 
the host resource's child
+ * resources.
+ *
+ * <p>
+ * Single-responsibility op-mixin carved out of the former {@code 
BasicGroupOperations} interface (and the
+ * concrete {@code getChildren(...)} method previously kept on the {@code 
Basic*Group} base classes). The
+ * navigation page renders one entry per {@link Rest#children() 
@Rest(children)} resource declared on the
+ * host.
+ *
+ * <h5 class='section'>Reaching the host's children:</h5>
+ *
+ * <p>
+ * A mixin's {@code @RestOp} methods are bound to a per-mixin {@link 
RestContext} sub-context that has no
+ * children of its own, so this op resolves the host's children via {@link 
RestMixin#getHostContext()
+ * getHostContext()}{@code .getRestChildren()} rather than against the mixin's 
own (empty) context.  Because
+ * {@code getHostContext()} is backed by the sub-context's already-populated 
parent linkage, this works
+ * whether the mixin is composed onto a servlet host or a child-resource host.
+ *
+ * <h5 class='section'>Page decoration:</h5>
+ *
+ * <p>
+ * Per the mixin sub-context inheritance model, the navigation page inherits 
the host's class-level
+ * {@link org.apache.juneau.html.annotation.HtmlDocConfig @HtmlDocConfig} page 
decoration (navigation links,
+ * etc.), so the rendered HTML matches the host's other endpoints.
+ *
+ * <h5 class='figure'>Composition example:</h5>
+ *
+ * <p class='bjava'>
+ *     <ja>@Rest</ja>(mixins=NavigationMixin.<jk>class</jk>, 
children={Foo.<jk>class</jk>, Bar.<jk>class</jk>})
+ *     <jk>public class</jk> RootResource <jk>extends</jk> RestServlet { ... }
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link ChildResourceDescriptions}
+ *     <li class='jm'>{@link RestMixin#getHostContext()}
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/RestServerComposition";>REST Server 
&mdash; Composition (mixins, paths)</a>
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+@Rest
+public class NavigationMixin extends RestMixin {
+
+       /** No-arg constructor &mdash; navigation has no configurable state. */
+       public NavigationMixin() {}
+
+       /**
+        * [GET /] - Get child resources.
+        *
+        * <p>
+        * Returns a bean that lists and allows navigation to the host 
resource's child resources, resolved
+        * against the {@linkplain RestMixin#getHostContext() host context} so 
the listing reflects the host's
+        * children rather than the mixin's own (empty) sub-context.
+        *
+        * @param req The HTTP request.
+        * @return The bean containing links to the child resources.
+        */
+       @RestGet(path="/", summary="Navigation page")
+       public ChildResourceDescriptions getChildren(RestRequest req) {
+               return new ChildResourceDescriptions(getHostContext(), req);
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestResourceGroup.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestResourceGroup.java
index 2060d99a69..8ea399bc49 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestResourceGroup.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestResourceGroup.java
@@ -20,6 +20,7 @@ import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.beans.*;
 import org.apache.juneau.rest.config.*;
+import org.apache.juneau.rest.ops.*;
 
 import jakarta.servlet.*;
 import jakarta.servlet.http.*;
@@ -37,8 +38,12 @@ import jakarta.servlet.http.*;
  * <p>
  * Adds the group-navigation endpoint ({@code GET /}) as a concrete method on 
top of the residual op-mixins
  * inherited from {@link BasicRestResource}. The navigation page is rendered 
as a method of the host resource
- * (rather than a sub-context mixin) so it inherits the host's {@link 
org.apache.juneau.html.annotation.HtmlDocConfig @HtmlDocConfig}
- * page decoration.
+ * (rather than the {@link NavigationMixin} sub-context mixin) so it inherits 
the host's
+ * {@link org.apache.juneau.html.annotation.HtmlDocConfig @HtmlDocConfig} page 
decoration &mdash; a mixin
+ * sub-context does not inherit the host's class-level {@code @HtmlDocConfig}, 
so a navigation op living on a
+ * mixin renders without the host's navlinks/aside/footer/theme.  {@link 
NavigationMixin} (backed by
+ * {@link RestMixin#getHostContext()}) is the mixin flavor for hosts where 
that page decoration is not
+ * required (e.g. JSON-only APIs).
  *
  * <p>
  * Children are attached to this resource using the {@link Rest#children() 
@Rest(children)} annotation.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServletGroup.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServletGroup.java
index a7163e13a9..43a3efb338 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServletGroup.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServletGroup.java
@@ -20,6 +20,7 @@ import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.beans.*;
 import org.apache.juneau.rest.config.*;
+import org.apache.juneau.rest.ops.*;
 
 import jakarta.servlet.*;
 
@@ -36,8 +37,12 @@ import jakarta.servlet.*;
  * <p>
  * Adds the group-navigation endpoint ({@code GET /}) as a concrete method on 
top of the residual op-mixins
  * inherited from {@link BasicRestServlet}. The navigation page is rendered as 
a method of the host resource
- * (rather than a sub-context mixin) so it inherits the host's {@link 
org.apache.juneau.html.annotation.HtmlDocConfig @HtmlDocConfig}
- * page decoration.
+ * (rather than the {@link NavigationMixin} sub-context mixin) so it inherits 
the host's
+ * {@link org.apache.juneau.html.annotation.HtmlDocConfig @HtmlDocConfig} page 
decoration &mdash; a mixin
+ * sub-context does not inherit the host's class-level {@code @HtmlDocConfig}, 
so a navigation op living on a
+ * mixin renders without the host's navlinks/aside/footer/theme.  {@link 
NavigationMixin} (backed by
+ * {@link RestMixin#getHostContext()}) is the mixin flavor for hosts where 
that page decoration is not
+ * required (e.g. JSON-only APIs).
  *
  * <p>
  * Children are attached to this resource using the {@link Rest#children() 
@Rest(children)} annotation.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java
index d97cd3eb16..9e8f0f8456 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java
@@ -16,6 +16,9 @@
  */
 package org.apache.juneau.rest.servlet;
 
+import java.util.concurrent.atomic.*;
+
+import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 
 /**
@@ -47,12 +50,85 @@ import org.apache.juneau.rest.annotation.*;
  * carries no builder or stashed-builder state and mixins are configured 
purely by their {@code @Rest} /
  * {@code @RestOp} annotations.
  *
+ * <h5 class='section'>Reaching the host resource:</h5>
+ *
+ * <p>
+ * Because a mixin's {@code @RestOp} methods are bound to a per-mixin {@link 
RestContext} sub-context (the
+ * sub-context has no children of its own), host-level introspection performed 
against the mixin's <i>own</i>
+ * context comes back empty.  {@link #getHostContext()} bridges that gap: it 
returns the {@link RestContext} of
+ * the host (mixed-into) resource so a mixin op can enumerate the host's child 
resources, swagger, stats, etc.
+ *
+ * <p class='bjava'>
+ *     <jc>// Render the host's child-resource navigation list from a mixin 
op.</jc>
+ *     <ja>@RestGet</ja>(path=<js>"/"</js>)
+ *     <jk>public</jk> ChildResourceDescriptions getChildren(RestRequest 
<jv>req</jv>) {
+ *             <jk>return new</jk> ChildResourceDescriptions(getHostContext(), 
<jv>req</jv>);
+ *     }
+ * </p>
+ *
  * <h5 class='section'>See Also:</h5><ul>
  *     <li class='jc'>{@link RestServlet}
  *     <li class='jc'>{@link RestResource}
+ *     <li class='jm'>{@link #getHostContext()}
  *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/RestServerComposition";>REST Server 
&mdash; Composition (mixins, paths)</a>
  * </ul>
  *
  * @since 9.5.0
  */
-public abstract class RestMixin {}
+public abstract class RestMixin {
+
+       /**
+        * The per-mixin {@link RestContext} sub-context this mixin instance is 
bound to, captured via
+        * {@link #setContext(RestContext)} when the host composes the mixin.
+        *
+        * <p>
+        * Remains {@code null} when a {@code RestMixin} subclass is 
instantiated directly (not composed via
+        * {@link Rest#mixins() @Rest(mixins=...)}), in which case {@link 
#getHostContext()} returns {@code null}.
+        */
+       private final AtomicReference<RestContext> context = new 
AtomicReference<>();
+
+       /**
+        * Captures the per-mixin {@link RestContext} sub-context this mixin 
instance is bound to.
+        *
+        * <p>
+        * Invoked reflectively by the host's {@code 
RestContext.buildMixinContext(...)} while composing the mixin
+        * (mirroring the {@code setContext(RestContext)} contract honored by 
{@link RestServlet} and
+        * {@link RestResource} for child resources).  The supplied context is 
the mixin sub-context whose
+        * {@link RestContext#getParentContext() parent} is the host &mdash; 
that linkage is what {@link #getHostContext()}
+        * reads.
+        *
+        * @param value The mixin sub-context. Must not be <jk>null</jk>.
+        */
+       protected void setContext(RestContext value) {
+               context.set(value);
+       }
+
+       /**
+        * Returns the {@link RestContext} of the host (mixed-into) resource 
this mixin is composed into.
+        *
+        * <p>
+        * A mixin's {@code @RestOp} methods are bound to a per-mixin 
sub-context that has no children of its own;
+        * this accessor returns the host context so a mixin op (or config-time 
code) can introspect the host
+        * &mdash; most commonly its child resources via {@link 
RestContext#getRestChildren()} for the navigation
+        * page.  It is backed by the mixin sub-context's already-populated
+        * {@link RestContext#getParentContext() parent linkage}, so it is 
usable at config time and does not depend
+        * on an in-flight request.
+        *
+        * <h5 class='section'>Edge cases:</h5><ul>
+        *      <li class='note'><b>Standalone / no host</b> &mdash; when a 
{@code RestMixin} subclass is instantiated
+        *              directly rather than composed via {@link Rest#mixins() 
@Rest(mixins=...)}, this returns {@code null},
+        *              mirroring {@link RestContext#getParentContext()}'s 
top-level contract.  Callers needing host-only
+        *              behavior should null-check.
+        *      <li class='note'><b>Nested mixins</b> &mdash; under nested 
{@code @Rest(mixins=...)} the flat-inheritance
+        *              rule collects every mixin as a mixin of the single 
top-level host, so this returns that top-level
+        *              host (never an intermediate mixin).
+        * </ul>
+        *
+        * @return The host resource's {@link RestContext}, or {@code null} 
when this mixin is not composed into a host.
+        * @since 9.5.0
+        */
+       public RestContext getHostContext() {
+               var c = context.get();
+               return c == null ? null : c.getParentContext();
+       }
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/NavigationMixin_AsMixin_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/NavigationMixin_AsMixin_Test.java
new file mode 100644
index 0000000000..2831844336
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/NavigationMixin_AsMixin_Test.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.ops;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.mock.classic.*;
+import org.apache.juneau.rest.servlet.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Validates {@link NavigationMixin} mounted as a mixin via {@code 
@Rest(mixins=...)} on a vanilla
+ * {@link RestServlet} host (TODO-146).
+ *
+ * <p>
+ * The navigation op lives on the mixin's (empty) sub-context but must 
enumerate the HOST's child resources
+ * via {@link RestMixin#getHostContext()}. These tests confirm the {@code GET 
/} navigation page lists the
+ * host's children rather than the mixin's own (empty) children.
+ *
+ * @since 9.5.0
+ */
+class NavigationMixin_AsMixin_Test extends TestBase {
+
+       @Rest(path="/c1", title="Child One") public static class Child1 extends 
BasicRestServlet { private static final long serialVersionUID = 1L; 
@RestGet(path="/") public String x() { return "c1"; } }
+       @Rest(path="/c2", title="Child Two") public static class Child2 extends 
BasicRestServlet { private static final long serialVersionUID = 1L; 
@RestGet(path="/") public String x() { return "c2"; } }
+
+       // Host extends BasicRestServlet (for serializers) and composes 
NavigationMixin directly, rather than
+       // inheriting it from a Basic*Group base, to prove the mixin enumerates 
the HOST's children.
+       @Rest(mixins=NavigationMixin.class, children={Child1.class, 
Child2.class})
+       public static class A extends BasicRestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       private static final MockRestClient a = 
MockRestClient.buildLax(A.class);
+
+       @Test void a01_navigationListsHostChildren() throws Exception {
+               var body = 
a.get("/").accept("application/json").run().assertStatus(200).getContent().asString();
+               assertTrue(body.contains("c1"), "Navigation page must list host 
child 'c1'; got: " + body);
+               assertTrue(body.contains("c2"), "Navigation page must list host 
child 'c2'; got: " + body);
+       }
+
+       @Test void a02_navigationNotEmpty() throws Exception {
+               var body = 
a.get("/").accept("application/json").run().assertStatus(200).getContent().asString();
+               assertNotEquals("[]", body.trim(), "Navigation page must not 
enumerate the mixin's own (empty) children.");
+       }
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/servlet/RestMixin_HostContext_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/servlet/RestMixin_HostContext_Test.java
new file mode 100644
index 0000000000..ca63f0e07f
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/servlet/RestMixin_HostContext_Test.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.servlet;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.mock.classic.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link RestMixin#getHostContext()} (TODO-146).
+ *
+ * <p>
+ * Verifies the opt-in host-context accessor: it returns the host {@link 
RestContext} when the mixin is
+ * composed via {@code @Rest(mixins=...)}, returns {@code null} when the mixin 
is used standalone, and
+ * resolves to the single flat top-level host under nested mixins (FINISHED-81 
flat-inheritance rule).
+ *
+ * @since 9.5.0
+ */
+class RestMixin_HostContext_Test extends TestBase {
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // a01: Composed as a mixin -> getHostContext() returns the host 
context.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Rest
+       public static class A_Mixin extends RestMixin {
+               @RestGet(path="/a") public String a() { return "a"; }
+       }
+
+       @Rest(mixins=A_Mixin.class)
+       public static class A_Host extends RestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a01_getHostContext_returnsHost_whenComposedAsMixin() {
+               var host = new A_Host();
+               MockRestClient.buildLax(host);
+               var hostCtx = host.getContext();
+               var mixinInstance = (RestMixin) 
hostCtx.getMixinContexts().get(A_Mixin.class).getResource();
+               assertSame(hostCtx, mixinInstance.getHostContext(),
+                       "getHostContext() must return the host context when 
composed as a mixin.");
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // a02: Standalone (never composed) -> getHostContext() returns null.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Rest
+       public static class A02_Mixin extends RestMixin {
+               @RestGet(path="/a") public String a() { return "a"; }
+       }
+
+       @Test void a02_getHostContext_returnsNull_standalone() {
+               assertNull(new A02_Mixin().getHostContext(),
+                       "getHostContext() must return null when the mixin is 
never composed into a host.");
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // a03: Nested mixins -> every mixin resolves to the same flat 
top-level host (no chaining).
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Rest
+       public static class A03_Inner extends RestMixin {
+               @RestGet(path="/inner") public String inner() { return "inner"; 
}
+       }
+
+       @Rest(mixins=A03_Inner.class)
+       public static class A03_Outer extends RestMixin {
+               @RestGet(path="/outer") public String outer() { return "outer"; 
}
+       }
+
+       @Rest(mixins=A03_Outer.class)
+       public static class A03_Host extends RestServlet {
+               private static final long serialVersionUID = 1L;
+       }
+
+       @Test void a03_getHostContext_flatTopHost_underNestedMixins() {
+               var host = new A03_Host();
+               MockRestClient.buildLax(host);
+               var hostCtx = host.getContext();
+               var outer = (RestMixin) 
hostCtx.getMixinContexts().get(A03_Outer.class).getResource();
+               var inner = (RestMixin) 
hostCtx.getMixinContexts().get(A03_Inner.class).getResource();
+               assertSame(hostCtx, outer.getHostContext(), "Outer mixin must 
resolve to the top-level host.");
+               assertSame(hostCtx, inner.getHostContext(),
+                       "Nested inner mixin must resolve to the SAME flat 
top-level host (not the outer mixin).");
+       }
+}
diff --git a/juneau-utest/test-run-history.tsv 
b/juneau-utest/test-run-history.tsv
index b425e61424..4c40380ad8 100644
--- a/juneau-utest/test-run-history.tsv
+++ b/juneau-utest/test-run-history.tsv
@@ -57,3 +57,4 @@ timestamp     git_sha branch  tests_run       failures        
errors  skipped surefire_sec    wall_sec
 2026-05-29T16:30:02Z   29bd5a598e5a    master  126063  0       0       21      
148
 2026-05-31T16:32:58Z   4369f411e8f9    master  126115  0       0       21      
153
 2026-06-01T13:18:45Z   c9d380738f06    master  126130  0       0       26      
184
+2026-06-01T14:07:35Z   40a74b4f9452    master  126135  0       0       26      
186

Reply via email to