diff --git a/README.md b/README.md index 30cf07021..adb9e0ea3 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ public class CopilotSDK { // Create a session var session = client.createSession( - new SessionConfig().setModel("claude-sonnet-4.5")).get(); + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5")).get(); // Handle assistant message events session.on(AssistantMessageEvent.class, msg -> { diff --git a/jbang-example.java b/jbang-example.java index 20838e288..ca30b2c10 100644 --- a/jbang-example.java +++ b/jbang-example.java @@ -15,7 +15,7 @@ public static void main(String[] args) throws Exception { // Create a session var session = client.createSession( - new SessionConfig().setModel("claude-sonnet-4.5")).get(); + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5")).get(); // Handle assistant message events session.on(AssistantMessageEvent.class, msg -> { diff --git a/src/site/markdown/advanced.md b/src/site/markdown/advanced.md index 66339d30e..0b516bdf1 100644 --- a/src/site/markdown/advanced.md +++ b/src/site/markdown/advanced.md @@ -66,7 +66,7 @@ var lookupTool = ToolDefinition.create( ); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setTools(List.of(lookupTool)) ).get(); ``` @@ -85,7 +85,7 @@ Use `APPEND` mode to add constraints while keeping default guardrails: ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setSystemMessage(new SystemMessageConfig() .setMode(SystemMessageMode.APPEND) .setContent(""" @@ -103,7 +103,7 @@ Use `REPLACE` mode for complete control (removes default guardrails): ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setSystemMessage(new SystemMessageConfig() .setMode(SystemMessageMode.REPLACE) .setContent("You are a helpful coding assistant.")) @@ -164,7 +164,7 @@ Supported providers: ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setProvider(new ProviderConfig() .setType("openai") .setBaseUrl("https://api.openai.com/v1") @@ -178,7 +178,7 @@ Some providers require bearer token authentication instead of API keys: ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setProvider(new ProviderConfig() .setType("openai") .setBaseUrl("https://my-custom-endpoint.example.com/v1") @@ -194,7 +194,7 @@ var session = client.createSession( ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setProvider(new ProviderConfig() .setType("openai") .setBaseUrl("http://localhost:/v1")) @@ -248,7 +248,7 @@ When enabled (default), the session automatically compacts older messages as the ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setInfiniteSessions(new InfiniteSessionConfig() .setEnabled(true) .setBackgroundCompactionThreshold(0.80) // Start compacting at 80% @@ -295,7 +295,7 @@ Map server = Map.of( ); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setMcpServers(Map.of("filesystem", server)) ).get(); ``` @@ -317,7 +317,7 @@ var reviewer = new CustomAgentConfig() .setTools(List.of("read_file", "search_code")); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setCustomAgents(List.of(reviewer)) ).get(); @@ -355,7 +355,7 @@ var agents = List.of( ); var session = client.createSession( - new SessionConfig().setCustomAgents(agents) + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setCustomAgents(agents) ).get(); ``` @@ -373,7 +373,7 @@ Skills are loaded from `SKILL.md` files in subdirectories of the specified skill ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setSkillDirectories(List.of("/path/to/skills")) ).get(); ``` @@ -397,7 +397,7 @@ Disable specific skills by name: ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setSkillDirectories(List.of("/path/to/skills")) .setDisabledSkills(List.of("my-skill")) ).get(); @@ -411,7 +411,7 @@ Use a custom configuration directory for session settings: ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setConfigDir("/path/to/custom/config") ).get(); ``` @@ -426,7 +426,7 @@ Handle user input requests when the AI uses the `ask_user` tool to gather inform ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setOnUserInputRequest((request, invocation) -> { System.out.println("Agent asks: " + request.getQuestion()); @@ -471,7 +471,7 @@ Approve or deny permission requests from the AI. ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setOnPermissionRequest((request, invocation) -> { // Inspect request and approve/deny var result = new PermissionRequestResult(); @@ -499,7 +499,7 @@ var hooks = new SessionHooks() }); var session = client.createSession( - new SessionConfig().setHooks(hooks) + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setHooks(hooks) ).get(); ``` diff --git a/src/site/markdown/cookbook/error-handling.md b/src/site/markdown/cookbook/error-handling.md index cb66b4934..db1a173f5 100644 --- a/src/site/markdown/cookbook/error-handling.md +++ b/src/site/markdown/cookbook/error-handling.md @@ -41,7 +41,7 @@ public class BasicErrorHandling { client.start().get(); var session = client.createSession( - new SessionConfig().setModel("gpt-5")).get(); + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5")).get(); session.on(AssistantMessageEvent.class, msg -> { System.out.println(msg.getData().getContent()); @@ -206,7 +206,7 @@ public class TryWithResources { client.start().get(); try (var session = client.createSession( - new SessionConfig().setModel("gpt-5")).get()) { + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5")).get()) { session.on(AssistantMessageEvent.class, msg -> { System.out.println(msg.getData().getContent()); @@ -251,7 +251,7 @@ public class ToolErrorHandling { client.start().get(); var session = client.createSession( - new SessionConfig().setTools(List.of(errorTool))).get(); + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setTools(List.of(errorTool))).get(); session.on(AssistantMessageEvent.class, msg -> { System.out.println(msg.getData().getContent()); diff --git a/src/site/markdown/cookbook/managing-local-files.md b/src/site/markdown/cookbook/managing-local-files.md index e66427704..941cbdded 100644 --- a/src/site/markdown/cookbook/managing-local-files.md +++ b/src/site/markdown/cookbook/managing-local-files.md @@ -48,7 +48,7 @@ public class ManagingLocalFiles { // Create session var session = client.createSession( - new SessionConfig().setModel("gpt-5")).get(); + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5")).get(); // Set up event handlers var done = new CountDownLatch(1); @@ -168,7 +168,7 @@ public class InteractiveFileOrganizer { client.start().get(); var session = client.createSession( - new SessionConfig().setModel("gpt-5")).get(); + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5")).get(); session.on(AssistantMessageEvent.class, msg -> System.out.println("\nCopilot: " + msg.getData().getContent()) diff --git a/src/site/markdown/cookbook/multiple-sessions.md b/src/site/markdown/cookbook/multiple-sessions.md index be5cb78b6..ff7441255 100644 --- a/src/site/markdown/cookbook/multiple-sessions.md +++ b/src/site/markdown/cookbook/multiple-sessions.md @@ -42,11 +42,11 @@ public class MultipleSessions { // Create multiple independent sessions var session1 = client.createSession( - new SessionConfig().setModel("gpt-5")).get(); + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5")).get(); var session2 = client.createSession( - new SessionConfig().setModel("gpt-5")).get(); + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5")).get(); var session3 = client.createSession( - new SessionConfig().setModel("claude-sonnet-4.5")).get(); + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5")).get(); // Set up event handlers for each session session1.on(AssistantMessageEvent.class, msg -> @@ -90,7 +90,7 @@ Use custom IDs for easier tracking: ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setSessionId("user-123-chat") .setModel("gpt-5") ).get(); @@ -129,9 +129,9 @@ public class ParallelSessions { public static void runParallelSessions(CopilotClient client) throws Exception { // Create sessions in parallel var sessionFutures = List.of( - client.createSession(new SessionConfig().setModel("gpt-5")), - client.createSession(new SessionConfig().setModel("gpt-5")), - client.createSession(new SessionConfig().setModel("claude-sonnet-4.5")) + client.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5")), + client.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-5")), + client.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5")) ); // Wait for all sessions to be created diff --git a/src/site/markdown/cookbook/persisting-sessions.md b/src/site/markdown/cookbook/persisting-sessions.md index 47b989940..a281c88f3 100644 --- a/src/site/markdown/cookbook/persisting-sessions.md +++ b/src/site/markdown/cookbook/persisting-sessions.md @@ -42,7 +42,7 @@ public class PersistingSessions { // Create session with a memorable ID var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setSessionId("user-123-conversation") .setModel("gpt-5") ).get(); @@ -73,7 +73,7 @@ public class ResumeSession { client.start().get(); // Resume the previous session - var session = client.resumeSession("user-123-conversation").get(); + var session = client.resumeSession("user-123-conversation", new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); session.on(AssistantMessageEvent.class, msg -> System.out.println(msg.getData().getContent()) @@ -135,7 +135,7 @@ public class SessionHistory { try (var client = new CopilotClient()) { client.start().get(); - var session = client.resumeSession("user-123-conversation").get(); + var session = client.resumeSession("user-123-conversation", new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); var messages = session.getMessages().get(); for (var event : messages) { @@ -184,7 +184,7 @@ public class SessionManager { System.out.print("Enter session ID: "); String sessionId = scanner.nextLine(); session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setSessionId(sessionId) .setModel("gpt-5") ).get(); @@ -195,7 +195,7 @@ public class SessionManager { System.out.print("Enter session ID to resume: "); String resumeId = scanner.nextLine(); try { - session = client.resumeSession(resumeId).get(); + session = client.resumeSession(resumeId, new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); System.out.println("Resumed session: " + resumeId); } catch (Exception ex) { System.err.println("Failed to resume session: " + ex.getMessage()); @@ -264,13 +264,13 @@ public class CheckSession { if (sessionExists(client, sessionId)) { System.out.println("Session exists, resuming..."); - var session = client.resumeSession(sessionId).get(); + var session = client.resumeSession(sessionId, new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); // ... use session ... session.close(); } else { System.out.println("Session doesn't exist, creating new one..."); var session = client.createSession( - new SessionConfig().setSessionId(sessionId).setModel("gpt-5") + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setSessionId(sessionId).setModel("gpt-5") ).get(); // ... use session ... session.close(); diff --git a/src/site/markdown/cookbook/pr-visualization.md b/src/site/markdown/cookbook/pr-visualization.md index 002a9f073..dfe9db85f 100644 --- a/src/site/markdown/cookbook/pr-visualization.md +++ b/src/site/markdown/cookbook/pr-visualization.md @@ -94,7 +94,7 @@ public class PRVisualization { """, owner, repoName, cwd); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("gpt-5") .setSystemMessage(new SystemMessageConfig().setContent(systemMessage)) ).get(); diff --git a/src/site/markdown/documentation.md b/src/site/markdown/documentation.md index e5f405307..01aba950f 100644 --- a/src/site/markdown/documentation.md +++ b/src/site/markdown/documentation.md @@ -36,7 +36,7 @@ try (var client = new CopilotClient()) { client.start().get(); var session = client.createSession( - new SessionConfig().setModel("gpt-4.1") + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-4.1") ).get(); var response = session.sendAndWait("Explain Java records in one sentence").get(); @@ -232,7 +232,7 @@ Enable streaming to receive response chunks as they're generated: ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("gpt-4.1") .setStreaming(true) ).get(); @@ -343,7 +343,7 @@ for (var model : models) { ```java var session = client.createSession( - new SessionConfig().setModel("claude-sonnet-4") + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4") ).get(); ``` @@ -355,7 +355,7 @@ For models that support it, control how much effort the model spends reasoning b ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("o3-mini") .setReasoningEffort("high") ).get(); @@ -382,7 +382,7 @@ Restrict the session to only specific tools: ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setAvailableTools(List.of("read_file", "search_code", "list_dir")) ).get(); ``` @@ -395,7 +395,7 @@ Allow all tools except specific ones: ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setExcludedTools(List.of("execute_command", "write_file")) ).get(); ``` @@ -410,7 +410,7 @@ Tool filtering applies to built-in tools. Your custom tools (registered via `set var lookupTool = ToolDefinition.create("lookup_issue", "Fetch issue", schema, handler); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setTools(List.of(lookupTool)) // Custom tool always available .setAvailableTools(List.of("read_file")) // Only allow read_file from built-ins ).get(); @@ -424,7 +424,7 @@ Set the working directory for file operations in the session: ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setWorkingDirectory("/path/to/project") ).get(); ``` @@ -553,11 +553,11 @@ session.send(new MessageOptions() ```java var session1 = client.createSession( - new SessionConfig().setModel("gpt-4.1") + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-4.1") ).get(); var session2 = client.createSession( - new SessionConfig().setModel("claude-sonnet-4") + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4") ).get(); // Send messages concurrently @@ -578,7 +578,7 @@ var lastSessionId = client.getLastSessionId().get(); var sessions = client.listSessions().get(); // Resume a session -var session = client.resumeSession(lastSessionId).get(); +var session = client.resumeSession(lastSessionId, new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); ``` ### Resume Options @@ -606,7 +606,7 @@ When resuming a session, you can optionally reconfigure many settings. This is u ```java // Resume with a different model -var config = new ResumeSessionConfig() +var config = new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("claude-sonnet-4") // Switch to a different model .setReasoningEffort("high"); // Increase reasoning effort @@ -655,7 +655,7 @@ Complete list of all `SessionConfig` options for `createSession()`: Use `clone()` to copy a base configuration before making per-session changes: ```java -var base = new SessionConfig() +var base = new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("gpt-4.1") .setStreaming(true); diff --git a/src/site/markdown/getting-started.md b/src/site/markdown/getting-started.md index 9077426f7..49281a4fc 100644 --- a/src/site/markdown/getting-started.md +++ b/src/site/markdown/getting-started.md @@ -74,7 +74,7 @@ public class HelloCopilot { client.start().get(); var session = client.createSession( - new SessionConfig().setModel("gpt-4.1") + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-4.1") ).get(); var response = session.sendAndWait( @@ -119,7 +119,7 @@ public class StreamingExample { client.start().get(); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("gpt-4.1") .setStreaming(true) ).get(); @@ -195,7 +195,7 @@ public class ToolExample { ); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("gpt-4.1") .setStreaming(true) .setTools(List.of(getWeather)) @@ -270,7 +270,7 @@ public class WeatherAssistant { ); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("gpt-4.1") .setStreaming(true) .setTools(List.of(getWeather)) diff --git a/src/site/markdown/hooks.md b/src/site/markdown/hooks.md index 4e36c9869..11df1d6e9 100644 --- a/src/site/markdown/hooks.md +++ b/src/site/markdown/hooks.md @@ -35,7 +35,7 @@ var hooks = new SessionHooks() }); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("gpt-4.1") .setHooks(hooks) ).get(); @@ -333,7 +333,7 @@ public class HooksExample { }); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("gpt-4.1") .setHooks(hooks) ).get(); @@ -370,7 +370,7 @@ var hooks = new SessionHooks() if (hooks.hasHooks()) { var session = client.createSession( - new SessionConfig().setHooks(hooks) + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setHooks(hooks) ).get(); } ``` diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index 9ea166715..27abc27c0 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -47,7 +47,7 @@ public class Example { client.start().get(); var session = client.createSession( - new SessionConfig().setModel("claude-sonnet-4.5") + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("claude-sonnet-4.5") ).get(); var done = new CompletableFuture(); @@ -93,7 +93,7 @@ class hello { public static void main(String[] args) throws Exception { try (var client = new CopilotClient()) { client.start().get(); - var session = client.createSession(new SessionConfig()).get(); + var session = client.createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); var done = new CompletableFuture(); session.on(AssistantMessageEvent.class, msg -> { System.out.print(msg.getData().getContent()); diff --git a/src/site/markdown/mcp.md b/src/site/markdown/mcp.md index 262b67372..68df0db87 100644 --- a/src/site/markdown/mcp.md +++ b/src/site/markdown/mcp.md @@ -17,7 +17,7 @@ Map server = Map.of( ); var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setMcpServers(Map.of("filesystem", server)) ).get(); @@ -83,7 +83,7 @@ Combine tools from several sources. ```java var session = client.createSession( - new SessionConfig() + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setMcpServers(Map.of( "filesystem", filesystemServer, "github", githubServer, diff --git a/src/site/markdown/setup.md b/src/site/markdown/setup.md index 1130808ff..56a2df4e5 100644 --- a/src/site/markdown/setup.md +++ b/src/site/markdown/setup.md @@ -22,7 +22,7 @@ The simplest setup uses the Copilot CLI already signed in on your development ma try (var client = new CopilotClient()) { client.start().get(); var session = client.createSession( - new SessionConfig().setModel("gpt-4.1") + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-4.1") ).get(); // Use session... } @@ -54,7 +54,7 @@ var options = new CopilotClientOptions() try (var client = new CopilotClient(options)) { client.start().get(); var session = client.createSession( - new SessionConfig().setModel("gpt-4.1") + new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setModel("gpt-4.1") ).get(); // Requests are made on behalf of the authenticated user } diff --git a/src/test/java/com/github/copilot/sdk/DocumentationSamplesTest.java b/src/test/java/com/github/copilot/sdk/DocumentationSamplesTest.java new file mode 100644 index 000000000..bb8a6f07e --- /dev/null +++ b/src/test/java/com/github/copilot/sdk/DocumentationSamplesTest.java @@ -0,0 +1,145 @@ +package com.github.copilot.sdk; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +class DocumentationSamplesTest { + + @Test + void docsAndJbangSamplesUseRequiredPermissionHandler() throws IOException { + for (Path path : documentationFiles()) { + String content = stripStringsAndComments(Files.readString(path)); + assertFalse(hasConfigWithoutPermissionHandler(content, "SessionConfig"), + () -> path + " contains SessionConfig sample without setOnPermissionRequest"); + assertFalse(hasConfigWithoutPermissionHandler(content, "ResumeSessionConfig"), + () -> path + " contains ResumeSessionConfig sample without setOnPermissionRequest"); + assertFalse(hasSingleArgumentResumeSessionCall(content), + () -> path + " contains removed resumeSession(String) overload"); + } + } + + private static boolean hasConfigWithoutPermissionHandler(String content, String configType) { + String constructor = "new " + configType + "()"; + int fromIndex = 0; + while (true) { + int start = content.indexOf(constructor, fromIndex); + if (start < 0) { + return false; + } + int end = content.indexOf(';', start); + if (end < 0) { + end = content.length(); + } + if (!content.substring(start, end).contains("setOnPermissionRequest(")) { + return true; + } + fromIndex = start + constructor.length(); + } + } + + private static boolean hasSingleArgumentResumeSessionCall(String content) { + int fromIndex = 0; + while (true) { + int callStart = content.indexOf("resumeSession(", fromIndex); + if (callStart < 0) { + return false; + } + int index = callStart + "resumeSession(".length(); + int depth = 1; + int topLevelCommaCount = 0; + while (index < content.length() && depth > 0) { + char c = content.charAt(index); + if (c == '(') { + depth++; + } else if (c == ')') { + depth--; + } else if (c == ',' && depth == 1) { + topLevelCommaCount++; + } + index++; + } + + if (depth == 0 && topLevelCommaCount == 0) { + return true; + } + fromIndex = callStart + 1; + } + } + + private static String stripStringsAndComments(String input) { + StringBuilder out = new StringBuilder(input.length()); + int i = 0; + while (i < input.length()) { + char c = input.charAt(i); + if (c == '"' || c == '\'') { + char quote = c; + out.append(' '); + i++; + while (i < input.length()) { + char current = input.charAt(i); + out.append(' '); + if (current == '\\') { + i++; + if (i < input.length()) { + out.append(' '); + } + } else if (current == quote) { + i++; + break; + } + i++; + } + continue; + } + if (c == '/' && i + 1 < input.length()) { + char next = input.charAt(i + 1); + if (next == '/') { + out.append(' ').append(' '); + i += 2; + while (i < input.length() && input.charAt(i) != '\n') { + out.append(' '); + i++; + } + continue; + } + if (next == '*') { + out.append(' ').append(' '); + i += 2; + while (i + 1 < input.length() && !(input.charAt(i) == '*' && input.charAt(i + 1) == '/')) { + out.append(' '); + i++; + } + if (i + 1 < input.length()) { + out.append(' ').append(' '); + i += 2; + } + continue; + } + } + out.append(c); + i++; + } + return out.toString(); + } + + private static List documentationFiles() throws IOException { + Path root = Path.of("").toAbsolutePath(); + List files = new ArrayList<>(); + files.add(root.resolve("README.md")); + files.add(root.resolve("jbang-example.java")); + + try (Stream markdownFiles = Files.walk(root.resolve("src/site/markdown"))) { + markdownFiles.filter(Files::isRegularFile).filter(path -> path.toString().endsWith(".md")) + .forEach(files::add); + } + return files; + } +}