/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ package com.github.copilot.sdk; import static org.junit.jupiter.api.Assertions.*; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.Test; import com.github.copilot.sdk.json.CreateSessionRequest; import com.github.copilot.sdk.json.ResumeSessionConfig; import com.github.copilot.sdk.json.ResumeSessionRequest; import com.github.copilot.sdk.json.SessionConfig; import com.github.copilot.sdk.json.SessionHooks; import com.github.copilot.sdk.json.ToolDefinition; import com.github.copilot.sdk.json.UserInputResponse; /** * Unit tests for {@link SessionRequestBuilder} branch coverage. *

* Exercises branches in buildCreateRequest, buildResumeRequest, and * configureSession that are not reached by E2E tests. */ public class SessionRequestBuilderTest { // ========================================================================= // buildCreateRequest // ========================================================================= @Test void testBuildCreateRequestNullConfig() { CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(null); assertNotNull(request); assertNull(request.getModel()); assertTrue(request.getRequestPermission(), "requestPermission should be true even for null config"); assertEquals("direct", request.getEnvValueMode(), "envValueMode should be 'direct' even for null config"); } @Test void testBuildCreateRequestHooksNonNullButEmpty() { // Hooks object exists but hasHooks() returns false var config = new SessionConfig().setHooks(new SessionHooks()); CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); assertNull(request.getHooks(), "Should be null when hooks are empty"); } @Test void testBuildCreateRequestHooksWithHandler() { var hooks = new SessionHooks().setOnPreToolUse((input, inv) -> CompletableFuture.completedFuture(null)); var config = new SessionConfig().setHooks(hooks); CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); assertTrue(request.getHooks(), "Should be true when hooks have handlers"); } @Test void testBuildCreateRequestSetsEnvValueModeToDirect() { CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(new SessionConfig()); assertEquals("direct", request.getEnvValueMode()); } @Test void testBuildCreateRequestAlwaysSetsRequestPermissionTrue() { // No permission handler set - requestPermission should still be true CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(new SessionConfig()); assertTrue(request.getRequestPermission(), "requestPermission should always be true to enable deny-by-default behavior"); } @Test void testBuildCreateRequestSetsClientName() { var config = new SessionConfig().setClientName("my-app"); CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); assertEquals("my-app", request.getClientName()); } // ========================================================================= // buildResumeRequest // ========================================================================= @Test void testBuildResumeRequestNullConfig() { ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-1", null); assertEquals("sid-1", request.getSessionId()); assertNull(request.getModel()); assertTrue(request.getRequestPermission(), "requestPermission should be true even for null config"); assertEquals("direct", request.getEnvValueMode(), "envValueMode should be 'direct' even for null config"); } @Test void testBuildResumeRequestWithTools() { var tool = ToolDefinition.create("my_tool", "A tool", Map.of("type", "object"), inv -> CompletableFuture.completedFuture("result")); var config = new ResumeSessionConfig().setTools(List.of(tool)); ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-2", config); assertNotNull(request.getTools()); assertEquals(1, request.getTools().size()); assertEquals("my_tool", request.getTools().get(0).name()); } @Test void testBuildResumeRequestWithUserInputHandler() { var config = new ResumeSessionConfig() .setOnUserInputRequest((req, inv) -> CompletableFuture.completedFuture(new UserInputResponse())); ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-3", config); assertTrue(request.getRequestUserInput()); } @Test void testBuildResumeRequestHooksNonNullButEmpty() { var config = new ResumeSessionConfig().setHooks(new SessionHooks()); ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-4", config); assertNull(request.getHooks(), "Should be null when hooks are empty"); } @Test void testBuildResumeRequestHooksWithHandler() { var hooks = new SessionHooks().setOnSessionEnd((input, inv) -> CompletableFuture.completedFuture(null)); var config = new ResumeSessionConfig().setHooks(hooks); ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-5", config); assertTrue(request.getHooks(), "Should be true when hooks have handlers"); } @Test void testBuildResumeRequestDisableResume() { var config = new ResumeSessionConfig().setDisableResume(true); ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-6", config); assertTrue(request.getDisableResume()); } @Test void testBuildResumeRequestStreaming() { var config = new ResumeSessionConfig().setStreaming(true); ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-7", config); assertTrue(request.getStreaming()); } @Test void testBuildResumeRequestSetsEnvValueModeToDirect() { ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-8", new ResumeSessionConfig()); assertEquals("direct", request.getEnvValueMode()); } @Test void testBuildResumeRequestAlwaysSetsRequestPermissionTrue() { // No permission handler set - requestPermission should still be true ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-9", new ResumeSessionConfig()); assertTrue(request.getRequestPermission(), "requestPermission should always be true to enable deny-by-default behavior"); } @Test void testBuildResumeRequestSetsClientName() { var config = new ResumeSessionConfig().setClientName("my-app"); ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-10", config); assertEquals("my-app", request.getClientName()); } // ========================================================================= // configureSession (ResumeSessionConfig overload) // ========================================================================= @Test void testConfigureResumeSessionNullConfig() throws Exception { var session = createTestSession(); // Should not throw SessionRequestBuilder.configureSession(session, (ResumeSessionConfig) null); } @Test void testConfigureResumeSessionWithTools() throws Exception { var session = createTestSession(); var tool = ToolDefinition.create("resume_tool", "desc", Map.of(), inv -> CompletableFuture.completedFuture("ok")); var config = new ResumeSessionConfig().setTools(List.of(tool)); SessionRequestBuilder.configureSession(session, config); assertNotNull(session.getTool("resume_tool")); } @Test void testConfigureResumeSessionWithUserInputHandler() throws Exception { var session = createTestSession(); var config = new ResumeSessionConfig() .setOnUserInputRequest((req, inv) -> CompletableFuture.completedFuture(new UserInputResponse())); SessionRequestBuilder.configureSession(session, config); // Handler was registered — verify by calling handleUserInputRequest // (package-private) var response = session.handleUserInputRequest(new com.github.copilot.sdk.json.UserInputRequest()).get(); assertNotNull(response); } @Test void testConfigureResumeSessionWithHooks() throws Exception { var session = createTestSession(); var hooks = new SessionHooks().setOnPreToolUse((input, inv) -> CompletableFuture.completedFuture(null)); var config = new ResumeSessionConfig().setHooks(hooks); SessionRequestBuilder.configureSession(session, config); // Hooks registered — handleHooksInvoke should dispatch preToolUse var mapper = JsonRpcClient.getObjectMapper(); var input = mapper.valueToTree(Map.of("toolName", "test_tool")); var result = session.handleHooksInvoke("preToolUse", input).get(); assertNull(result); // handler returns null } // ========================================================================= // Helper // ========================================================================= private CopilotSession createTestSession() throws Exception { var constructor = CopilotSession.class.getDeclaredConstructor(String.class, JsonRpcClient.class, String.class); constructor.setAccessible(true); return constructor.newInstance("builder-test-session", null, null); } @Test void testBuildCreateRequestWithAgent() { var config = new SessionConfig().setAgent("my-agent"); CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config, "test-session-id"); assertEquals("my-agent", request.getAgent()); } @Test void testBuildResumeRequestWithAgent() { var config = new ResumeSessionConfig().setAgent("my-agent"); ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("session-id", config); assertEquals("my-agent", request.getAgent()); } // ========================================================================= // extractTransformCallbacks // ========================================================================= @Test void extractTransformCallbacks_nullSystemMessage_returnsNull() { ExtractedTransforms result = SessionRequestBuilder.extractTransformCallbacks(null); assertNull(result.wireSystemMessage()); assertNull(result.transformCallbacks()); } @Test void extractTransformCallbacks_appendMode_returnsOriginalConfig() { var config = new com.github.copilot.sdk.json.SystemMessageConfig() .setMode(com.github.copilot.sdk.SystemMessageMode.APPEND).setContent("extra content"); ExtractedTransforms result = SessionRequestBuilder.extractTransformCallbacks(config); assertSame(config, result.wireSystemMessage()); assertNull(result.transformCallbacks()); } @Test void extractTransformCallbacks_customizeModeNoTransforms_returnsOriginalConfig() { var sections = Map.of("tone", new com.github.copilot.sdk.json.SectionOverride() .setAction(com.github.copilot.sdk.json.SectionOverrideAction.REMOVE)); var config = new com.github.copilot.sdk.json.SystemMessageConfig() .setMode(com.github.copilot.sdk.SystemMessageMode.CUSTOMIZE).setSections(sections); ExtractedTransforms result = SessionRequestBuilder.extractTransformCallbacks(config); assertSame(config, result.wireSystemMessage()); assertNull(result.transformCallbacks()); } @Test void extractTransformCallbacks_customizeModeWithTransform_extractsCallbacks() { var transformFn = (java.util.function.Function>) content -> CompletableFuture .completedFuture(content + " modified"); var sections = Map.of("identity", new com.github.copilot.sdk.json.SectionOverride().setTransform(transformFn)); var config = new com.github.copilot.sdk.json.SystemMessageConfig() .setMode(com.github.copilot.sdk.SystemMessageMode.CUSTOMIZE).setSections(sections); ExtractedTransforms result = SessionRequestBuilder.extractTransformCallbacks(config); // Wire config should be different from original assertNotSame(config, result.wireSystemMessage()); // Callbacks should be extracted assertNotNull(result.transformCallbacks()); assertTrue(result.transformCallbacks().containsKey("identity")); // Wire config should have transform action instead of callback assertNotNull(result.wireSystemMessage().getSections()); var wireSection = result.wireSystemMessage().getSections().get("identity"); assertNotNull(wireSection); assertEquals(com.github.copilot.sdk.json.SectionOverrideAction.TRANSFORM, wireSection.getAction()); assertNull(wireSection.getTransform()); } }