66
77import static org .junit .jupiter .api .Assertions .assertEquals ;
88import static org .junit .jupiter .api .Assertions .assertNotNull ;
9- import static org .junit .jupiter .api .Assertions .assertTrue ;
109
11- import java .util .Map ;
12- import java .util .concurrent .CompletableFuture ;
13- import java .util .concurrent .ConcurrentHashMap ;
14- import java .util .concurrent .TimeUnit ;
10+ import java .lang .reflect .Field ;
11+ import java .lang .reflect .Modifier ;
12+ import java .util .Arrays ;
13+ import java .util .Set ;
14+ import java .util .stream .Collectors ;
1515
16- import org .junit .jupiter .api .AfterAll ;
17- import org .junit .jupiter .api .BeforeAll ;
1816import org .junit .jupiter .api .Test ;
1917
20- import com .github .copilot .rpc .CopilotClientOptions ;
21- import com .github .copilot .rpc .MessageOptions ;
22- import com .github .copilot .rpc .PermissionHandler ;
23- import com .github .copilot .rpc .SectionOverride ;
24- import com .github .copilot .rpc .SessionConfig ;
25- import com .github .copilot .rpc .SystemMessageConfig ;
2618import com .github .copilot .rpc .SystemMessageSections ;
2719import com .github .copilot .rpc .SystemPromptSections ;
2820
2921/**
3022 * Failsafe integration test that validates {@link SystemMessageSections}
31- * constants are recognized by the live Copilot CLI runtime.
32- * <p>
33- * Uses a transform callback on the {@code identity} section to assert the
34- * runtime invokes the callback with non-empty content — proving the constant is
35- * a valid section identifier understood by the runtime.
36- * <p>
37- * Requires the CLI to be installed and the user to be signed in. Uses
38- * {@link TestUtil#findCliPath()} so the test harness binary is found in CI.
23+ * constants and the backward-compatible inheritance from
24+ * {@link SystemPromptSections}.
3925 */
4026@ SuppressWarnings ("deprecation" )
4127class SystemMessageSectionsIT {
4228
43- private static CopilotClient client ;
44-
45- @ BeforeAll
46- static void setup () throws Exception {
47- String cliPath = TestUtil .findCliPath ();
48- CopilotClientOptions options = new CopilotClientOptions ().setCliPath (cliPath ).setUseLoggedInUser (true );
49- client = new CopilotClient (options );
50- client .start ().get (30 , TimeUnit .SECONDS );
51- }
52-
53- @ AfterAll
54- static void teardown () throws Exception {
55- if (client != null ) {
56- client .close ();
57- }
58- }
59-
60- /**
61- * Verifies that a transform callback on {@link SystemMessageSections#IDENTITY}
62- * is invoked by the runtime with non-empty section content.
63- * <p>
64- * This proves the constant {@code "identity"} is a real section ID that the
65- * runtime recognizes and populates.
66- */
67- @ Test
68- void transformOnIdentitySectionReceivesNonEmptyContent () throws Exception {
69- // Thread-safe container to capture what the runtime passes to our transform
70- ConcurrentHashMap <String , String > capturedContent = new ConcurrentHashMap <>();
71-
72- var systemMessage = new SystemMessageConfig ().setMode (SystemMessageMode .CUSTOMIZE )
73- .setSections (Map .of (SystemMessageSections .IDENTITY , new SectionOverride ().setTransform (content -> {
74- capturedContent .put ("identity" , content );
75- return CompletableFuture .completedFuture (content );
76- }), SystemMessageSections .TONE , new SectionOverride ().setTransform (content -> {
77- capturedContent .put ("tone" , content );
78- return CompletableFuture .completedFuture (content );
79- })));
80-
81- CopilotSession session = client .createSession (new SessionConfig ().setSystemMessage (systemMessage )
82- .setOnPermissionRequest (PermissionHandler .APPROVE_ALL )).get (30 , TimeUnit .SECONDS );
83-
84- try {
85- // Send a message to trigger the runtime to build the system message
86- // (transforms fire during session creation or first message)
87- session .sendAndWait (new MessageOptions ().setPrompt ("Say hello" ), 60_000 ).get (90 , TimeUnit .SECONDS );
88-
89- // Assert: identity transform was invoked with non-empty content
90- String identityContent = capturedContent .get ("identity" );
91- assertNotNull (identityContent , "Expected identity transform callback to be invoked by the runtime" );
92- assertTrue (!identityContent .isBlank (), "Expected identity section content to be non-empty but was blank" );
93-
94- // Assert: tone transform was also invoked
95- String toneContent = capturedContent .get ("tone" );
96- assertNotNull (toneContent , "Expected tone transform callback to be invoked by the runtime" );
97- assertTrue (!toneContent .isBlank (), "Expected tone section content to be non-empty but was blank" );
98- } finally {
99- session .close ();
100- }
101- }
102-
10329 /**
10430 * Verifies that the deprecated {@link SystemPromptSections} constants resolve
10531 * to the same values as {@link SystemMessageSections} — ensuring backward
10632 * compatibility.
10733 */
10834 @ Test
10935 void deprecatedSystemPromptSectionsMatchesSystemMessageSections () {
110- // These are compile-time constants so this test guards against accidental
111- // divergence if someone edits one class but not the other.
11236 assertEquals (SystemMessageSections .IDENTITY , SystemPromptSections .IDENTITY );
11337 assertEquals (SystemMessageSections .TONE , SystemPromptSections .TONE );
11438 assertEquals (SystemMessageSections .TOOL_EFFICIENCY , SystemPromptSections .TOOL_EFFICIENCY );
@@ -121,4 +45,39 @@ void deprecatedSystemPromptSectionsMatchesSystemMessageSections() {
12145 assertEquals (SystemMessageSections .RUNTIME_INSTRUCTIONS , SystemPromptSections .RUNTIME_INSTRUCTIONS );
12246 assertEquals (SystemMessageSections .LAST_INSTRUCTIONS , SystemPromptSections .LAST_INSTRUCTIONS );
12347 }
48+
49+ /**
50+ * Verifies that {@link SystemPromptSections} extends
51+ * {@link SystemMessageSections} — confirming the sealed hierarchy is correctly
52+ * wired.
53+ */
54+ @ Test
55+ void systemPromptSectionsExtendsSystemMessageSections () {
56+ assertEquals (SystemMessageSections .class , SystemPromptSections .class .getSuperclass ());
57+ }
58+
59+ /**
60+ * Verifies that every {@code public static final String} field declared in
61+ * {@link SystemMessageSections} is accessible via the deprecated
62+ * {@link SystemPromptSections} (inheritance test).
63+ */
64+ @ Test
65+ void allConstantsInheritedByDeprecatedClass () throws Exception {
66+ Set <String > parentConstants = Arrays .stream (SystemMessageSections .class .getDeclaredFields ())
67+ .filter (f -> Modifier .isPublic (f .getModifiers ()) && Modifier .isStatic (f .getModifiers ())
68+ && Modifier .isFinal (f .getModifiers ()) && f .getType () == String .class )
69+ .map (Field ::getName ).collect (Collectors .toSet ());
70+
71+ // Verify there are constants (sanity check)
72+ assertNotNull (parentConstants );
73+ assertEquals (11 , parentConstants .size (), "Expected 11 section constants in SystemMessageSections" );
74+
75+ // Each constant should be accessible via the subclass
76+ for (String constantName : parentConstants ) {
77+ Field parentField = SystemMessageSections .class .getDeclaredField (constantName );
78+ Field childField = SystemPromptSections .class .getField (constantName );
79+ assertEquals (parentField .get (null ), childField .get (null ),
80+ "Constant " + constantName + " should have same value in both classes" );
81+ }
82+ }
12483}
0 commit comments