Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,8 @@ public struct BushelCloudKitService: Sendable, RecordManaging, CloudKitRecordCol
///
/// This is the protocol-conforming version that doesn't track create vs update.
/// For detailed tracking, use the overload with `classification` parameter.
public func executeBatchOperations(
_ operations: [RecordOperation],
recordType: String
) async throws {
// Create empty classification (no tracking)
public func executeBatchOperations(_ operations: [RecordOperation]) async throws {
guard let recordType = operations.first?.recordType else { return }
let classification = OperationClassification(proposedRecords: [], existingRecords: [])
_ = try await executeBatchOperations(
operations, recordType: recordType, classification: classification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal struct MockCloudKitServiceTests {
fields: record.toCloudKitFields()
)

try await service.executeBatchOperations([operation], recordType: "RestoreImage")
try await service.executeBatchOperations([operation])

let storedRecords = await service.getStoredRecords(ofType: "RestoreImage")
#expect(storedRecords.count == 1)
Expand All @@ -79,7 +79,7 @@ internal struct MockCloudKitServiceTests {
recordName: recordName,
fields: initialRecord.toCloudKitFields()
)
try await service.executeBatchOperations([createOp], recordType: "RestoreImage")
try await service.executeBatchOperations([createOp])

// Replace with updated record
let updatedRecord = RestoreImageRecord(
Expand All @@ -103,7 +103,7 @@ internal struct MockCloudKitServiceTests {
recordName: recordName,
fields: updatedRecord.toCloudKitFields()
)
try await service.executeBatchOperations([replaceOp], recordType: "RestoreImage")
try await service.executeBatchOperations([replaceOp])

// Verify only one record exists with updated data
let storedRecords = await service.getStoredRecords(ofType: "RestoreImage")
Expand All @@ -130,15 +130,15 @@ internal struct MockCloudKitServiceTests {
recordName: recordName,
fields: record.toCloudKitFields()
)
try await service.executeBatchOperations([createOp], recordType: "RestoreImage")
try await service.executeBatchOperations([createOp])

// Delete record
let deleteOp = RecordOperation(
operationType: .delete,
recordType: "RestoreImage",
recordName: recordName
)
try await service.executeBatchOperations([deleteOp], recordType: "RestoreImage")
try await service.executeBatchOperations([deleteOp])

// Verify record is gone
let storedRecords = await service.getStoredRecords(ofType: "RestoreImage")
Expand Down Expand Up @@ -170,11 +170,8 @@ internal struct MockCloudKitServiceTests {
),
]

try await service.executeBatchOperations(
Array(operations[0...1]),
recordType: "RestoreImage"
)
try await service.executeBatchOperations([operations[2]], recordType: "XcodeVersion")
try await service.executeBatchOperations(Array(operations[0...1]))
try await service.executeBatchOperations([operations[2]])

let restoreImages = await service.getStoredRecords(ofType: "RestoreImage")
let xcodeVersions = await service.getStoredRecords(ofType: "XcodeVersion")
Expand Down Expand Up @@ -213,7 +210,7 @@ internal struct MockCloudKitServiceTests {
)

do {
try await service.executeBatchOperations([operation], recordType: "RestoreImage")
try await service.executeBatchOperations([operation])
Issue.record("Expected error to be thrown")
} catch is MockCloudKitError {
// Success - error was thrown as expected
Expand Down Expand Up @@ -244,8 +241,8 @@ internal struct MockCloudKitServiceTests {
)
]

try await service.executeBatchOperations(batch1, recordType: "RestoreImage")
try await service.executeBatchOperations(batch2, recordType: "XcodeVersion")
try await service.executeBatchOperations(batch1)
try await service.executeBatchOperations(batch2)

let history = await service.getOperationHistory()
#expect(history.count == 2)
Expand All @@ -264,7 +261,7 @@ internal struct MockCloudKitServiceTests {
recordName: "test",
fields: TestFixtures.sonoma1421.toCloudKitFields()
)
try await service.executeBatchOperations([operation], recordType: "RestoreImage")
try await service.executeBatchOperations([operation])

// Clear storage
await service.clearStorage()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ internal struct CloudKitErrorHandlingTests {
)

do {
try await service.executeBatchOperations([operation], recordType: "RestoreImage")
try await service.executeBatchOperations([operation])
Issue.record("Expected quota exceeded error to be thrown")
} catch let error as MockCloudKitError {
if case .quotaExceeded = error {
Expand All @@ -78,7 +78,7 @@ internal struct CloudKitErrorHandlingTests {
)

do {
try await service.executeBatchOperations([operation], recordType: "XcodeVersion")
try await service.executeBatchOperations([operation])
Issue.record("Expected reference validation error to be thrown")
} catch let error as MockCloudKitError {
if case .validatingReferenceError = error {
Expand All @@ -105,7 +105,7 @@ internal struct CloudKitErrorHandlingTests {
)

do {
try await service.executeBatchOperations([operation], recordType: "RestoreImage")
try await service.executeBatchOperations([operation])
Issue.record("Expected conflict error to be thrown")
} catch let error as MockCloudKitError {
if case .conflict = error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,16 @@ internal actor MockCloudKitService: RecordManaging {
return storedRecords[recordType] ?? []
}

internal func executeBatchOperations(
_ operations: [RecordOperation],
recordType: String
) async throws {
internal func executeBatchOperations(_ operations: [RecordOperation]) async throws {
operationHistory.append(operations)

if shouldFailModify {
throw modifyError ?? MockCloudKitError.networkError
}

// Process operations
// Each operation carries its own record type
for operation in operations {
let recordType = operation.recordType
switch operation.operationType {
case .create, .forceReplace:
handleCreateOrReplace(operation, recordType: recordType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,27 +72,12 @@ public struct CurrentUserCommand: MistDemoCommand, OutputFormatting {
// Create CloudKit client
let client = try MistKitClientFactory.create(for: config.base)

// Fetch current user information
let userInfo = try await client.fetchCaller()

// Filter fields if requested
let filteredUser = filterUserFields(userInfo, fields: config.fields)

// Format and output result
try await outputResult(filteredUser, format: config.output)
try await outputResult(userInfo, format: config.output)
} catch {
throw CurrentUserError.operationFailed(error.localizedDescription)
}
}

/// Filter user fields based on requested fields
/// Since UserInfo constructor is internal, we work with the original object
/// and filter during output instead
private func filterUserFields(_ userInfo: UserInfo, fields _: [String]?) -> UserInfo {
// Since we can't create new UserInfo instances, return the original
// Field filtering will be handled in the output methods
userInfo
}
}

// CurrentUserError is now defined in Errors/CurrentUserError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import MistKit

extension AuthenticationHelper {
internal static func setupServerToServer(
apiToken _: String,
keyID: String,
privateKey: String?,
privateKeyFile: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ internal enum AuthenticationHelper {
) async throws -> AuthenticationResult {
if let keyID {
return try await setupServerToServer(
apiToken: apiToken,
keyID: keyID,
privateKey: privateKey,
privateKeyFile: privateKeyFile,
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/MistKit/Protocols/RecordManaging+Generic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ extension RecordManaging {
let batches = operations.chunked(into: 200)

for batch in batches {
try await executeBatchOperations(batch, recordType: T.cloudKitRecordType)
try await executeBatchOperations(batch)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ extension RecordManaging where Self: CloudKitRecordCollection {
}

// Execute batch operation for this record type
try await executeBatchOperations(operations, recordType: typeName)
try await executeBatchOperations(operations)
}
}

Expand Down Expand Up @@ -168,7 +168,7 @@ extension RecordManaging where Self: CloudKitRecordCollection {
}

// Execute batch delete operations
try await executeBatchOperations(operations, recordType: typeName)
try await executeBatchOperations(operations)

deletedByType[typeName] = records.count
totalDeleted += records.count
Expand Down
9 changes: 4 additions & 5 deletions Sources/MistKit/Protocols/RecordManaging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,12 @@ public protocol RecordManaging {
/// Execute a batch of record operations
///
/// Handles batching operations to respect CloudKit's 200 operations/request limit.
/// Provides detailed progress reporting and error tracking.
/// Each `RecordOperation` carries its own record type, so no separate
/// `recordType` parameter is required.
///
/// - Parameters:
/// - operations: Array of record operations to execute
/// - recordType: The record type being operated on (for logging)
/// - Parameter operations: Array of record operations to execute
/// - Throws: CloudKit errors if the batch operations fail
func executeBatchOperations(_ operations: [RecordOperation], recordType _: String) async throws
func executeBatchOperations(_ operations: [RecordOperation]) async throws

/// Query all records of a specific type, automatically paginating
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@ extension CloudKitService: RecordManaging {
}

/// Execute a batch of record operations via modify
public func executeBatchOperations(
_ operations: [RecordOperation],
recordType _: String
) async throws {
public func executeBatchOperations(_ operations: [RecordOperation]) async throws {
_ = try await self.modifyRecords(
operations,
database: .public(.prefers(.serverToServer))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,40 +78,6 @@ extension CloudKitService {
try await fetchCaller()
}

/// Discover all user identities in the caller's CloudKit address book.
///
/// Hits CloudKit's GET `users/discover` endpoint. Routed against the public
/// database with web-auth credentials.
///
/// > Important: Marked `unavailable` until #28 is resolved — see issue for
/// > the live-testing investigation log.
@available(
*, unavailable,
message: "Not yet ready: GET /users/discover returns HTTP 500 in live testing. See #28."
)
public func discoverAllUserIdentities() async throws(CloudKitError) -> [UserIdentity] {
do {
let client = try self.client(for: .public(.requires(.webAuth)))
let response = try await client.discoverAllUserIdentities(
.init(
path: Operations.discoverAllUserIdentities.Input.Path(
containerIdentifier: containerIdentifier,
environment: environment,
database: .public(.requires(.webAuth))
)
)
)

let discoverData: Components.Schemas.DiscoverResponse =
try await responseProcessor.processDiscoverAllUserIdentitiesResponse(
response
)
return discoverData.users?.map(UserIdentity.init(from:)) ?? []
} catch {
throw mapToCloudKitError(error, context: "discoverAllUserIdentities")
}
}

/// Look up user identities by email address.
///
/// Hits CloudKit's POST `users/lookup/email` endpoint. Each requested email
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,32 +74,6 @@ extension CloudKitResponseProcessor {
}
}

/// Process discoverAllUserIdentities response.
///
/// Marked unavailable in lockstep with `CloudKitService.discoverAllUserIdentities()`.
/// The body throws `CloudKitError.unsupportedOperationType` so any stray
/// caller (for example via `@testable import` under Swift 6.1, where the
/// `@available(*, unavailable)` cascade does not apply) gets a recoverable
/// error rather than a crash. When #28 is resolved, restore the
/// protocol-generic implementation and re-add the `CloudKitResponseType`
/// conformance for `Operations.discoverAllUserIdentities.Output`.
///
/// The `@available(*, unavailable)` attribute is gated to Swift 6.2+ because
/// Swift 6.1 rejects calls to an unavailable function from within another
/// unavailable function; 6.2 relaxed that rule. Once Swift 6.1 is dropped
/// from the support matrix, delete the `#if swift(>=6.2)`/`#endif` lines so
/// the attribute always applies.
#if swift(>=6.2)
@available(*, unavailable, message: "Pending #28: discoverAllUserIdentities is not yet ready.")
#endif
internal func processDiscoverAllUserIdentitiesResponse(
_: Operations.discoverAllUserIdentities.Output
) async throws(CloudKitError) -> Components.Schemas.DiscoverResponse {
throw CloudKitError.unsupportedOperationType(
"discoverAllUserIdentities is not yet ready (pending #28)"
)
}

/// Process lookupUsersByEmail response
internal func processLookupUsersByEmailResponse(
_ response: Operations.lookupUsersByEmail.Output
Expand Down
4 changes: 2 additions & 2 deletions Tests/MistKitTests/Mocks/ResponseConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ extension ResponseConfig {
.networkError(URLError(.networkConnectionLost))
}

/// Creates a successful query response
internal static func successfulQuery(records _: [String: Any] = [:]) -> ResponseConfig {
/// Creates a successful query response with an empty records body
internal static func successfulQuery() -> ResponseConfig {
let responseJSON = """
{
"records": []
Expand Down
4 changes: 2 additions & 2 deletions Tests/MistKitTests/Mocks/ResponseProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ internal actor ResponseProvider {
}

/// Response provider for successful query operations
internal static func successfulQuery(records: [String: Any] = [:]) -> ResponseProvider {
ResponseProvider(defaultResponse: .successfulQuery(records: records))
internal static func successfulQuery() -> ResponseProvider {
ResponseProvider(defaultResponse: .successfulQuery())
}

/// Response provider that simulates a request timeout.
Expand Down
Loading
Loading