Peekaboo/peekaboo-cli/Tests/peekabooTests/JSONOutputTests.swift
Peter Steinberger fbf32f8e21 Prepare for beta.14 release: comprehensive test improvements and code cleanup
- Fixed all Swift test compilation errors and SwiftLint violations
- Enhanced test host app with permission status display and CLI availability checking
- Refactored ImageCommand.swift to improve readability and reduce function length
- Updated all tests to use proper Swift Testing patterns
- Added comprehensive local testing framework for screenshot functionality
- Updated documentation with proper test execution instructions
- Applied SwiftFormat to all Swift files and achieved zero serious linting issues

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-08 02:00:44 +01:00

382 lines
13 KiB
Swift

import Foundation
@testable import peekaboo
import Testing
@Suite("JSONOutput Tests", .tags(.jsonOutput, .unit))
struct JSONOutputTests {
// MARK: - AnyCodable Tests
@Test("AnyCodable encoding with various types", .tags(.fast))
func anyCodableEncodingVariousTypes() throws {
// Test string
let stringValue = AnyCodable("test string")
let stringData = try JSONEncoder().encode(stringValue)
let stringResult = try JSONSerialization.jsonObject(with: stringData) as? String
#expect(stringResult == "test string")
// Test number
let numberValue = AnyCodable(42)
let numberData = try JSONEncoder().encode(numberValue)
let numberResult = try JSONSerialization.jsonObject(with: numberData) as? Int
#expect(numberResult == 42)
// Test boolean
let boolValue = AnyCodable(true)
let boolData = try JSONEncoder().encode(boolValue)
let boolResult = try JSONSerialization.jsonObject(with: boolData) as? Bool
#expect(boolResult == true)
// Test null (using optional nil)
let nilValue: String? = nil
let nilAnyCodable = AnyCodable(nilValue as Any)
let nilData = try JSONEncoder().encode(nilAnyCodable)
let nilString = String(data: nilData, encoding: .utf8)
#expect(nilString == "null")
}
@Test("AnyCodable with nested structures", .tags(.fast))
func anyCodableNestedStructures() throws {
// Test array
let arrayValue = AnyCodable([1, 2, 3])
let arrayData = try JSONEncoder().encode(arrayValue)
let arrayResult = try JSONSerialization.jsonObject(with: arrayData) as? [Int]
#expect(arrayResult == [1, 2, 3])
// Test dictionary
let dictValue = AnyCodable(["key": "value", "number": 42])
let dictData = try JSONEncoder().encode(dictValue)
let dictResult = try JSONSerialization.jsonObject(with: dictData) as? [String: Any]
#expect(dictResult?["key"] as? String == "value")
#expect(dictResult?["number"] as? Int == 42)
}
@Test("AnyCodable decoding", .tags(.fast))
func anyCodableDecoding() throws {
// Test decoding from JSON
let jsonString = #"{"string": "test", "number": 42, "bool": true, "null": null}"#
let jsonData = Data(jsonString.utf8)
let decoded = try JSONDecoder().decode([String: AnyCodable].self, from: jsonData)
#expect(decoded["string"]?.value as? String == "test")
#expect(decoded["number"]?.value as? Int == 42)
#expect(decoded["bool"]?.value as? Bool == true)
#expect(decoded["null"]?.value == nil)
}
// MARK: - AnyEncodable Tests
@Test("AnyEncodable with custom types", .tags(.fast))
func anyEncodableCustomTypes() throws {
// Test with ApplicationInfo
let appInfo = ApplicationInfo(
app_name: "Test App",
bundle_id: "com.test.app",
pid: 1234,
is_active: true,
window_count: 2
)
// Test encoding through AnyCodable instead
let anyCodable = AnyCodable(appInfo)
let data = try JSONEncoder().encode(anyCodable)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
#expect(json?["app_name"] as? String == "Test App")
#expect(json?["bundle_id"] as? String == "com.test.app")
#expect(json?["pid"] as? Int32 == 1234)
#expect(json?["is_active"] as? Bool == true)
#expect(json?["window_count"] as? Int == 2)
}
// MARK: - JSON Output Function Tests
@Test("outputJSON function with success data", .tags(.fast))
func outputJSONSuccess() throws {
// Test data
let testData = ApplicationListData(applications: [
ApplicationInfo(
app_name: "Finder",
bundle_id: "com.apple.finder",
pid: 123,
is_active: true,
window_count: 1
)
])
// Test JSON serialization directly without capturing stdout
let encoder = JSONEncoder()
let data = try encoder.encode(testData)
let jsonString = String(data: data, encoding: .utf8) ?? ""
// Verify JSON structure
#expect(jsonString.contains("Finder"))
#expect(jsonString.contains("com.apple.finder"))
#expect(!jsonString.isEmpty)
}
@Test("CodableJSONResponse structure", .tags(.fast))
func codableJSONResponseStructure() throws {
let testData = ["test": "value"]
let response = CodableJSONResponse(
success: true,
data: testData,
messages: nil,
debug_logs: []
)
let encoder = JSONEncoder()
let data = try encoder.encode(response)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
#expect(json?["success"] as? Bool == true)
#expect((json?["data"] as? [String: Any])?["test"] as? String == "value")
#expect(json?["error"] == nil)
}
@Test("Error output JSON formatting", .tags(.fast))
func errorOutputJSONFormatting() throws {
// Test error JSON structure directly
let errorInfo = ErrorInfo(
message: "Test error message",
code: .APP_NOT_FOUND,
details: "Additional error details"
)
let response = JSONResponse(
success: false,
data: nil,
messages: nil,
error: errorInfo
)
let encoder = JSONEncoder()
let data = try encoder.encode(response)
let jsonString = String(data: data, encoding: .utf8) ?? ""
// Verify error JSON structure
#expect(jsonString.contains("\"success\":false") || jsonString.contains("\"success\": false"))
#expect(jsonString.contains("\"error\""))
#expect(jsonString.contains("Test error message"))
#expect(jsonString.contains("APP_NOT_FOUND"))
}
// MARK: - Edge Cases and Error Handling
@Test("AnyCodable with complex nested data", .tags(.fast))
func anyCodableComplexNestedData() throws {
let complexData: [String: Any] = [
"simple": "string",
"nested": [
"array": [1, 2, 3],
"dict": ["key": "value"],
"mixed": [
"string",
42,
true,
["nested": "array"]
]
]
]
let anyCodable = AnyCodable(complexData)
let encoded = try JSONEncoder().encode(anyCodable)
let decoded = try JSONSerialization.jsonObject(with: encoded) as? [String: Any]
#expect(decoded?["simple"] as? String == "string")
#expect((decoded?["nested"] as? [String: Any]) != nil)
}
@Test("JSON encoding performance with large data", .tags(.performance))
func jsonEncodingPerformance() throws {
// Create large dataset
var largeAppList: [ApplicationInfo] = []
for index in 0..<100 {
let appInfo = ApplicationInfo(
app_name: "App \(index)",
bundle_id: "com.test.app\(index)",
pid: Int32(1000 + index),
is_active: index.isMultiple(of: 2),
window_count: index % 10
)
largeAppList.append(appInfo)
}
let data = ApplicationListData(applications: largeAppList)
// Measure encoding performance
let startTime = CFAbsoluteTimeGetCurrent()
let encoded = try JSONEncoder().encode(data)
let encodingTime = CFAbsoluteTimeGetCurrent() - startTime
#expect(!encoded.isEmpty)
#expect(encodingTime < 1.0) // Should encode within 1 second
}
@Test("Thread safety of JSON operations", .tags(.concurrency))
func threadSafetyJSONOperations() async {
await withTaskGroup(of: Bool.self) { group in
for index in 0..<10 {
group.addTask {
do {
let appInfo = ApplicationInfo(
app_name: "App \(index)",
bundle_id: "com.test.app\(index)",
pid: Int32(1000 + index),
is_active: true,
window_count: 1
)
// Test encoding through AnyCodable instead
let anyCodable = AnyCodable(appInfo)
_ = try JSONEncoder().encode(anyCodable)
return true
} catch {
return false
}
}
}
var successCount = 0
for await success in group where success {
successCount += 1
}
#expect(successCount == 10)
}
}
@Test("Memory usage with repeated JSON operations", .tags(.memory))
func memoryUsageJSONOperations() {
// Test memory doesn't grow excessively with repeated JSON operations
for _ in 1...100 {
let data = ApplicationInfo(
app_name: "Test",
bundle_id: "com.test",
pid: 123,
is_active: true,
window_count: 1
)
do {
let encoded = try JSONEncoder().encode(data)
#expect(!encoded.isEmpty)
} catch {
Issue.record("JSON encoding should not fail: \(error)")
}
}
}
@Test("Error code enum completeness", .tags(.fast))
func errorCodeEnumCompleteness() {
// Test that all error codes have proper raw values
let errorCodes: [ErrorCode] = [
.PERMISSION_ERROR_SCREEN_RECORDING,
.PERMISSION_ERROR_ACCESSIBILITY,
.APP_NOT_FOUND,
.AMBIGUOUS_APP_IDENTIFIER,
.WINDOW_NOT_FOUND,
.CAPTURE_FAILED,
.FILE_IO_ERROR,
.INVALID_ARGUMENT,
.UNKNOWN_ERROR
]
for errorCode in errorCodes {
#expect(!errorCode.rawValue.isEmpty)
#expect(errorCode.rawValue.allSatisfy(\.isASCII))
}
}
}
// MARK: - Extension Test Suite for Output Format Validation
@Suite("JSON Output Format Validation", .tags(.jsonOutput, .integration))
struct JSONOutputFormatValidationTests {
@Test("MCP protocol compliance", .tags(.integration))
func mcpProtocolCompliance() throws {
// Test that JSON output follows MCP protocol format
let testData = ApplicationListData(applications: [])
let response = CodableJSONResponse(
success: true,
data: testData,
messages: nil,
debug_logs: []
)
let encoder = JSONEncoder()
// Properties are already in snake_case, no conversion needed
let data = try encoder.encode(response)
// Verify it's valid JSON
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
#expect(json != nil) // JSON was successfully created
// Verify required MCP fields
#expect(json?["success"] != nil)
#expect(json?["data"] != nil)
}
@Test("Snake case conversion consistency", .tags(.fast))
func snakeCaseConversionConsistency() throws {
let appInfo = ApplicationInfo(
app_name: "Test App",
bundle_id: "com.test.app",
pid: 1234,
is_active: true,
window_count: 2
)
let encoder = JSONEncoder()
// Properties are already in snake_case, no conversion needed
let data = try encoder.encode(appInfo)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
// Verify snake_case conversion
#expect(json?["app_name"] != nil)
#expect(json?["bundle_id"] != nil)
#expect(json?["is_active"] != nil)
#expect(json?["window_count"] != nil)
// Verify no camelCase keys exist
#expect(json?["appName"] == nil)
#expect(json?["bundleId"] == nil)
#expect(json?["isActive"] == nil)
#expect(json?["windowCount"] == nil)
}
@Test("Large data structure serialization", .tags(.performance))
func largeDataStructureSerialization() throws {
// Create a complex data structure
var windows: [WindowInfo] = []
for index in 0..<100 {
let window = WindowInfo(
window_title: "Window \(index)",
window_id: UInt32(1000 + index),
window_index: index,
bounds: WindowBounds(xCoordinate: index * 10, yCoordinate: index * 10, width: 800, height: 600),
is_on_screen: index.isMultiple(of: 2)
)
windows.append(window)
}
let windowData = WindowListData(
windows: windows,
target_application_info: TargetApplicationInfo(
app_name: "Test App",
bundle_id: "com.test.app",
pid: 1234
)
)
let startTime = CFAbsoluteTimeGetCurrent()
let encoded = try JSONEncoder().encode(windowData)
let duration = CFAbsoluteTimeGetCurrent() - startTime
#expect(!encoded.isEmpty)
#expect(duration < 0.5) // Should complete within 500ms
// Verify the JSON is valid
_ = try JSONSerialization.jsonObject(with: encoded)
#expect(Bool(true)) // JSON was successfully created
}
}