Skip to main content
Glama
AllCommandsJSONOutputTests.swift8.83 kB
import Foundation import Testing @testable import PeekabooCLI #if !PEEKABOO_SKIP_AUTOMATION @Suite( "All Commands JSON Output Support", .serialized, .tags(.integration), .enabled(if: CLITestEnvironment.runAutomationRead) ) struct AllCommandsJSONOutputTests { private static let commandsRequiringJSONOutput: [[String]] = [ // Basic commands ["image"], ["permissions"], ["see"], ["click"], ["type"], ["scroll"], ["hotkey"], ["swipe"], ["run"], ["sleep"], ["clean"], ["drag"], ["agent"], // List subcommands ["list", "apps"], ["list", "windows"], ["list", "permissions"], // Config subcommands ["config", "init"], ["config", "show"], ["config", "edit"], ["config", "validate"], // Window subcommands ["window", "close"], ["window", "minimize"], ["window", "maximize"], ["window", "focus"], ["window", "move"], ["window", "resize"], // App subcommands ["app", "launch"], ["app", "quit"], ["app", "hide"], ["app", "unhide"], ["app", "switch"], ["app", "list"], // Menu subcommands ["menu", "click"], // Dock subcommands ["dock", "launch"], ["dock", "right-click"], ["dock", "show"], ["dock", "hide"], ["dock", "list"], // Dialog subcommands ["dialog", "click"], ["dialog", "input"], ["dialog", "file"], ["dialog", "dismiss"], ["dialog", "list"], ] @Test("All commands support --json-output flag") func verifyAllCommandsHaveJSONOutputFlag() async throws { var missingJSONOutputCommands: [String] = [] for commandArgs in Self.commandsRequiringJSONOutput { let commandName = commandArgs.joined(separator: " ") let result = try await InProcessCommandRunner.runShared(commandArgs + ["--help"]) let output = result.combinedOutput if !output.contains("--json-output") { missingJSONOutputCommands.append(commandName) } } #expect( missingJSONOutputCommands.isEmpty, "Commands missing --json-output flag: \(missingJSONOutputCommands.joined(separator: ", "))" ) } @Test("Commands produce valid JSON with --json-output") func verifyCommandsProduceValidJSON() async throws { // Commands that can be safely tested without side effects let testableCommands: [(args: [String], description: String)] = [ (["permissions", "--json-output"], "permissions"), (["permissions", "check", "--json-output"], "permissions check"), (["list", "apps", "--json-output"], "list apps"), (["list", "permissions", "--json-output"], "list permissions"), (["sleep", "50", "--json-output"], "sleep"), (["clean", "--all-sessions", "--dry-run", "--json-output"], "clean (dry run)"), ] var invalidJSONCommands: [String] = [] for (commandArgs, description) in testableCommands { let result = try await InProcessCommandRunner.runShared(commandArgs) let outputString = result.combinedOutput // Skip empty output (some commands might not output anything in test environment) guard !outputString.isEmpty else { continue } // Try to parse as JSON do { let jsonData = outputString.data(using: .utf8) ?? Data() _ = try JSONSerialization.jsonObject(with: jsonData) } catch { invalidJSONCommands.append("\(description): \(error.localizedDescription)") } } #expect( invalidJSONCommands.isEmpty, "Commands producing invalid JSON: \(invalidJSONCommands.joined(separator: "\n"))" ) } @Test("JSON output follows consistent schema") func verifyJSONOutputSchema() async throws { let result = try await InProcessCommandRunner.runShared(["permissions", "--json-output"]) let data = Data(result.combinedOutput.utf8) guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { Issue.record("Failed to parse JSON output from permissions command") return } // Verify standard JSON schema #expect(json["success"] != nil, "JSON should contain 'success' field") if let success = json["success"] as? Bool { if success { // Successful responses should have data let hasData = json["data"] != nil let hasOtherFields = json.keys.count > 1 #expect( hasData || hasOtherFields, "Successful JSON responses should contain data or other result fields" ) } else { // Failed responses should have error #expect( json["error"] != nil, "Failed JSON responses should contain 'error' field" ) if let error = json["error"] as? [String: Any] { #expect(error["message"] != nil, "Error should contain 'message'") #expect(error["code"] != nil, "Error should contain 'code'") } } } } @Test("Error responses use JSON format") func verifyErrorResponsesUseJSON() async throws { // Test commands that will produce errors let errorCommands: [(args: [String], description: String)] = [ (["image", "--app", "NonExistentApp_XYZ_123", "--json-output"], "image missing app"), (["see", "--app", "NonExistentApp_XYZ_123", "--json-output"], "see missing app"), (["sleep", "--json-output", "--", "-100"], "negative sleep duration"), (["run", "missing.peekaboo.json", "--json-output"], "missing automation script"), ] var nonJSONErrors: [String] = [] for (commandArgs, description) in errorCommands { let result = try await InProcessCommandRunner.runWithSharedServices(commandArgs) let outputString = result.stdout let errorString = result.stderr // Try to find JSON in either output let jsonString = !outputString.isEmpty ? outputString : errorString guard !jsonString.isEmpty else { nonJSONErrors.append("\(description): No output produced") continue } // Try to parse as JSON do { let jsonData = jsonString.data(using: .utf8) ?? Data() if let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] { // Verify it's an error response let isError = json["success"] as? Bool == false let hasError = json["error"] != nil if !isError || !hasError { nonJSONErrors.append("\(description): JSON doesn't follow error format") } } else { nonJSONErrors.append("\(description): Not a JSON object") } } catch { nonJSONErrors.append("\(description): Invalid JSON - \(error)") } } #expect( nonJSONErrors.isEmpty, "Commands not producing JSON errors: \(nonJSONErrors.joined(separator: "\n"))" ) } @Test("Subcommands properly inherit JSON output") func verifySubcommandsInheritJSONOutput() async throws { // Test that subcommands can be called with --json-output let subcommandTests: [(args: [String], description: String)] = [ (["app", "list", "--json-output"], "app list"), (["config", "show", "--json-output"], "config show"), (["list", "permissions", "--json-output"], "list permissions"), ] var failedSubcommands: [String] = [] for (commandArgs, description) in subcommandTests { let result = try await InProcessCommandRunner.runWithSharedServices(commandArgs) if result.exitStatus != 0 { let errorString = result.stderr if errorString.contains("Unknown option"), errorString.contains("json-output") { failedSubcommands.append(description) } } } #expect( failedSubcommands.isEmpty, "Subcommands not accepting --json-output: \(failedSubcommands.joined(separator: ", "))" ) } } #endif

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/steipete/Peekaboo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server