Skip to content

Conversation

@leogdion
Copy link
Member

@leogdion leogdion commented Jan 9, 2026

…, #46, #30)

Implement three CloudKit Web Services operations to improve API coverage from 35% to 53% (9/17 operations):

  • lookupZones(): Batch zone lookup by ID with validation
  • fetchRecordChanges(): Incremental sync with pagination support (manual and automatic)
  • uploadAssets(): Binary asset uploads with multipart/form-data encoding

New public types:

  • ZoneID: Zone identifier (zoneName, ownerName)
  • RecordChangesResult: Change result with syncToken and moreComing flag
  • AssetUploadToken/AssetUploadResult: Upload tokens for record association

MistDemo integration tests:

  • Add --test-lookup-zones flag for zone lookup demonstrations
  • Add --test-fetch-changes flag with --fetch-all and --sync-token support
  • Add --test-upload-asset flag with optional --create-record association

Created GitHub issues for future enhancements:

All operations follow MistKit patterns: async/await, typed errors, proper logging, and comprehensive error handling.


Perform an AI-assisted review on CodePeer.com

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Jan 9, 2026

Codecov Report

❌ Patch coverage is 0% with 317 lines in your changes missing coverage. Please review.
✅ Project coverage is 13.63%. Comparing base (aee13f2) to head (ecc682c).

Files with missing lines Patch % Lines
...s/MistKit/Service/CloudKitService+Operations.swift 0.00% 115 Missing ⚠️
...tKit/Service/CloudKitService+WriteOperations.swift 0.00% 56 Missing ⚠️
...es/MistKit/Service/CloudKitResponseProcessor.swift 0.00% 39 Missing ⚠️
...Service/Operations.fetchRecordChanges.Output.swift 0.00% 24 Missing ⚠️
Sources/MistKit/Service/CloudKitService.swift 0.00% 21 Missing ⚠️
...istKit/Service/Operations.lookupZones.Output.swift 0.00% 17 Missing ⚠️
...stKit/Service/Operations.uploadAssets.Output.swift 0.00% 17 Missing ⚠️
Sources/MistKit/Service/AssetUploadToken.swift 0.00% 12 Missing ⚠️
Sources/MistKit/Service/RecordChangesResult.swift 0.00% 8 Missing ⚠️
Sources/MistKit/Service/ZoneID.swift 0.00% 8 Missing ⚠️
Additional details and impacted files
@@                Coverage Diff                 @@
##           v1.0.0-alpha.4     #204      +/-   ##
==================================================
- Coverage           14.29%   13.63%   -0.66%     
==================================================
  Files                  67       73       +6     
  Lines                7200     7517     +317     
==================================================
- Hits                 1029     1025       -4     
- Misses               6171     6492     +321     
Flag Coverage Δ
spm 13.63% <0.00%> (-0.61%) ⬇️
swift-6.1-jammy 13.63% <0.00%> (-0.61%) ⬇️
swift-6.1-noble 13.63% <0.00%> (-0.62%) ⬇️
swift-6.2-jammy 13.63% <0.00%> (-0.61%) ⬇️
swift-6.2-noble 13.63% <0.00%> (-0.66%) ⬇️
swift-6.3-jammynightly 13.63% <0.00%> (-0.61%) ⬇️
swift-6.3-noblenightly 13.63% <0.00%> (-0.61%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@claude
Copy link

claude bot commented Jan 9, 2026

Code Review: Add lookupZones, fetchRecordChanges, and uploadAssets operations

Summary

This PR adds three important CloudKit Web Services operations, improving API coverage from 35% to 53%. The implementation is well-structured and follows MistKit patterns consistently. Great work overall! 🎉

✅ Strengths

Code Quality

  • Excellent documentation: All public APIs have comprehensive DocC-style comments with clear examples
  • Consistent patterns: Follows established MistKit conventions (async/await, typed errors, logging)
  • Type safety: Good use of Swift's type system with Sendable, Equatable, and Hashable conformance
  • Error handling: Comprehensive error handling with proper CloudKitError mapping
  • Clean separation: New types in dedicated files following single-responsibility principle

Architecture

  • Protocol-oriented design: CloudKitResponseType conformance for all operation outputs
  • Proper abstraction: Clean separation between public API and internal OpenAPI integration
  • Sendable compliance: All types are properly marked as Sendable for concurrency safety
  • Validation: Input validation (empty arrays, size limits) at API boundaries

Demo Integration

  • Excellent UX: Well-designed CLI flags with clear help text
  • Practical examples: Demonstrates real-world usage patterns
  • Good user feedback: Clear output with emojis and formatting

🔍 Issues & Concerns

Critical: Missing Test Coverage ⚠️

This is the most significant issue. No unit tests were found for the three new operations:

  • No tests for lookupZones()
  • No tests for fetchRecordChanges() / fetchAllRecordChanges()
  • No tests for uploadAssets()

Impact: Without tests, we cannot verify:

  • Correct handling of success responses
  • Proper error mapping (400, 401, 403, etc.)
  • Validation logic (empty arrays, size limits)
  • Response parsing (especially edge cases like empty tokens, missing fields)
  • Pagination logic in fetchAllRecordChanges()

Recommendation: Add comprehensive unit tests following the pattern established in Tests/MistKitTests/Service/CloudKitServiceQueryTests.swift:

@Test("lookupZones returns zones successfully")
func testLookupZonesSuccess() async throws {
    // Mock URLSession with expected response
    // Verify correct zones returned
}

@Test("lookupZones handles empty zoneIDs")
func testLookupZonesEmptyValidation() async throws {
    // Verify throws proper error for empty array
}

@Test("fetchAllRecordChanges handles pagination")
func testFetchAllRecordChangesMultiplePages() async throws {
    // Mock multiple pages with moreComing flags
    // Verify all records accumulated correctly
}

Medium: Potential Memory Issues

Location: CloudKitService+Operations.swift:425-449

public func fetchAllRecordChanges(...) async throws -> (records: [RecordInfo], syncToken: String?) {
    var allRecords: [RecordInfo] = []
    // ...
    while moreComing {
        let result = try await fetchRecordChanges(...)
        allRecords.append(contentsOf: result.records) // ⚠️ Unbounded accumulation
    }
}

Issue: The method accumulates all records in memory, which could cause problems for zones with thousands of changes.

Recommendation: The documentation warning is good, but consider:

  1. Adding a maxRecords parameter to cap total accumulation
  2. Creating GitHub issue Enhancement: Add AsyncSequence wrapper for fetchRecordChanges streaming #200 (AsyncSequence wrapper) should be prioritized
  3. Consider making this method internal until the streaming solution exists

Low: API Design Considerations

1. Asset Upload Size Validation

Location: CloudKitService+WriteOperations.swift:197

let maxSize: Int = 250 * 1024 * 1024 // 250 MB

Observation: The 250 MB limit is hardcoded. CloudKit's actual limits might vary by account type or change over time.

Suggestion: Consider making this configurable or moving to a central constants file:

public enum CloudKitLimits {
    public static let maxAssetSize: Int = 250 * 1024 * 1024
}

2. Empty Data Validation Order

Location: CloudKitService+WriteOperations.swift:206-212

guard !data.isEmpty else {
    throw CloudKitError.httpErrorWithRawResponse(...)
}

Minor: Empty data check happens after size check. While both are correct, checking isEmpty first would be more efficient (O(1) vs reading data.count).

3. ZoneID Default Owner Name

Location: ZoneID.swift:46

The distinction between nil (current user) and an explicit owner name is clear in docs, but could benefit from a convenience property:

public var isCurrentUser: Bool { ownerName == nil }

Low: Documentation Improvements

1. Missing Usage Example in AssetUploadToken

The AssetUploadResult type is well-documented, but AssetUploadToken could use a standalone example showing how tokens are used in record association.

2. Sync Token Lifecycle

The fetchRecordChanges documentation is excellent, but could mention:

  • How long sync tokens remain valid
  • What happens if a token expires
  • Whether tokens are database-specific or zone-specific

🔒 Security Review

No security concerns identified

  • Proper token masking in logs via MistKitLogger
  • No sensitive data exposed in error messages
  • File size validation prevents DoS via large uploads
  • Input validation on all user-provided parameters

⚡ Performance Considerations

Asset Upload

The current implementation loads the entire file into memory:

let data = try Data(contentsOf: fileURL)

For large files (approaching 250 MB), consider:

  1. Streaming upload (if CloudKit API supports it)
  2. Memory-mapped file reading for large assets
  3. Progress tracking (tracked in issue Enhancement: Add progress tracking for large asset uploads #202 ✅)

Pagination Performance

The fetchAllRecordChanges loop is efficient for the common case, but consider:

  • Rate limiting between requests (CloudKit has request quotas)
  • Cancellation support for long-running fetches
  • Backoff strategy if moreComing pagination fails

📋 Best Practices Compliance

✅ Follows CLAUDE.md Guidelines

  • Uses async/await consistently
  • All types are Codable and Sendable
  • Proper use of swift-log for cross-platform logging
  • Follows the protocol-oriented design principle
  • Error handling with typed CloudKitError
  • Good documentation with DocC comments

✅ Follows SwiftLint Type Order

The code follows the default SwiftLint type ordering:

  1. Associated types
  2. Type properties
  3. Instance properties
  4. Initializers
  5. Type methods
  6. Instance methods
  7. Subscripts
  8. Nested types

📝 Minor Style/Consistency Notes

  1. Consistent error messages: All validation errors use the httpErrorWithRawResponse pattern - excellent consistency

  2. Logging discipline: All catch blocks properly log errors with appropriate log levels and redaction settings

  3. Internal vs public separation: Good use of internal init for converting from OpenAPI schemas

🎯 Recommendations

Must Address (Before Merge)

  1. Add comprehensive unit tests for all three operations
  2. Consider adding integration tests to the test suite (beyond MistDemo)

Should Address (High Priority)

  1. Add maxRecords parameter to fetchAllRecordChanges() or make it internal
  2. Document sync token validity and expiration behavior

Nice to Have (Future PRs)

  1. Create constants file for CloudKit limits
  2. Add convenience properties to ZoneID (isCurrentUser)
  3. Consider streaming upload support for large assets
  4. Prioritize issue Enhancement: Add AsyncSequence wrapper for fetchRecordChanges streaming #200 (AsyncSequence wrapper) to address memory concerns

🏆 Conclusion

This is high-quality work that significantly expands MistKit's CloudKit API coverage. The implementation is clean, well-documented, and follows established patterns.

The primary concern is the missing test coverage, which should be addressed before merging to ensure reliability and prevent regressions.

Once tests are added, this PR will be ready to merge! 🚀


Review performed by Claude Sonnet 4.5 via claude.ai/code

@meszmate
Copy link

Hi, I'm trying to use the .uploadAssets function on this branch, however it fails with the following error message:
BadRequestException: Unexpected character ('-' (code 45)) in numeric value: expected digit (0-9) to follow minus sign, for valid numeric value
at [Source: REDACTED (StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION disabled); line: 1, column: 2]

Any ETA when it becomes stable? I want to use it for mp3 audio assets.
Really appreciate the work you’ve done on this library!

@leogdion
Copy link
Member Author

Hi, I'm trying to use the .uploadAssets function on this branch, however it fails with the following error message: BadRequestException: Unexpected character ('-' (code 45)) in numeric value: expected digit (0-9) to follow minus sign, for valid numeric value at [Source: REDACTED (StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION disabled); line: 1, column: 2]

Any ETA when it becomes stable? I want to use it for mp3 audio assets. Really appreciate the work you’ve done on this library!

I'm in the very early stages of adding this. It's good to know someone is interested. I'll bump this up in my priorities for now. Probably in the next 2 weeks or so.

I need to:

  • fix the actual implementation.
  • Create integration tests in MistDemo
  • Unit Tests
  • PR reviews

leogdion and others added 2 commits January 19, 2026 17:28
…#46, #30)

Implement three CloudKit Web Services operations to improve API coverage from 35% to 53% (9/17 operations):

- lookupZones(): Batch zone lookup by ID with validation
- fetchRecordChanges(): Incremental sync with pagination support (manual and automatic)
- uploadAssets(): Binary asset uploads with multipart/form-data encoding

New public types:
- ZoneID: Zone identifier (zoneName, ownerName)
- RecordChangesResult: Change result with syncToken and moreComing flag
- AssetUploadToken/AssetUploadResult: Upload tokens for record association

MistDemo integration tests:
- Add --test-lookup-zones flag for zone lookup demonstrations
- Add --test-fetch-changes flag with --fetch-all and --sync-token support
- Add --test-upload-asset flag with optional --create-record association

Created GitHub issues for future enhancements:
- #200: AsyncSequence wrapper for fetchRecordChanges streaming
- #201: Batch asset upload support (multiple files)
- #202: Upload progress tracking for large files
- #203: Automatic token retry logic for expired tokens

All operations follow MistKit patterns: async/await, typed errors, proper logging, and comprehensive error handling.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements proper CLI subcommand architecture and integration tests that
demonstrate the three new CloudKit operations (lookupZones, fetchRecordChanges,
uploadAssets) working together in realistic workflows.

New CLI Subcommands:
- upload-asset: Upload binary assets to CloudKit
- lookup-zones: Look up specific zones by name
- fetch-changes: Fetch record changes with incremental sync
- test-integration: Run comprehensive 8-phase integration tests

Integration Test Suite (8 Phases):
1. Zone verification with lookupZones
2. Asset upload with uploadAssets (programmatic PNG generation)
3. Record creation with uploaded assets
4. Initial sync with fetchRecordChanges
5. Record modifications
6. Incremental sync demonstrating sync token usage
7. Final zone verification
8. Automatic cleanup

Infrastructure:
- CloudKitCommand protocol for shared functionality across subcommands
- IntegrationTestRunner orchestrates all test phases
- IntegrationTestData generates test PNG images programmatically
- IntegrationTestError provides typed error handling
- schema.ckdb defines MistKitIntegrationTest record type

Architecture Changes:
- Converted MistDemo from flag-based to subcommand architecture
- Added Commands/ directory for subcommand implementations
- Added Integration/ directory for test infrastructure
- CloudKitCommand protocol resolves API tokens from env or options

Documentation:
- README-INTEGRATION-TESTS.md with complete usage guide
- Schema deployment instructions
- Troubleshooting guide
- Example outputs for all commands

All subcommands support:
- API token from --api-token or CLOUDKIT_API_TOKEN env var
- Container identifier configuration
- Development/production environment selection

Test integration features:
- Configurable record count (--record-count)
- Configurable asset size (--asset-size)
- Verbose mode for detailed output (--verbose)
- Skip cleanup flag for manual inspection (--skip-cleanup)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@leogdion leogdion force-pushed the 199-cloudkit-api-coverage branch from 1badf6d to 4cc91cf Compare January 20, 2026 12:41
leogdion and others added 2 commits January 22, 2026 15:36
…ew CloudKit APIs

- Add private database support to integration tests with AdaptiveTokenManager
- Implement web authentication server with CloudKit.js integration
- Add web auth token options to CLI commands (--web-auth-token)
- Support CLOUDKIT_WEBAUTH_TOKEN environment variable
- Update integration test runner to handle both public/private databases
- Add comprehensive CloudKit.js authentication flow with multiple token extraction methods
- Create browser-based authentication interface with proper error handling
- Document testing status and next steps in TESTING_STATUS.md

New CLI options:
- test-integration --database [public|private] --web-auth-token TOKEN
- All commands now support web authentication for private database access

Authentication flow:
1. swift run mistdemo auth (starts web server)
2. Browser-based Apple ID sign-in with CloudKit.js
3. Automatic web auth token extraction and display
4. Use token with integration tests and individual operations

Ready to test lookupZones, fetchRecordChanges, and uploadAssets APIs
once web authentication token extraction is working correctly.

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

Co-Authored-By: Claude <noreply@anthropic.com>
…g status

MAJOR IMPROVEMENTS:
- Enhanced postMessage listener with origin verification (icloud.com, apple-cloudkit.com)
- Added network request interception (fetch/XHR) as fallback token capture method
- Extended timeout from 5s to 10s for token arrival
- Added browser debugging helpers (mistKitDebug.*)
- Simplified handleAuthentication() removing 160+ lines of non-working detection code

IMPLEMENTATION DETAILS:
Phase 1: Enhanced postMessage capture
  - Origin validation for security
  - Support for multiple token formats (plain string `158__54__...`, object properties)
  - Global token storage in window.cloudKitWebAuthToken

Phase 2: Network interception fallback
  - Intercepts fetch() and XMLHttpRequest
  - Captures tokens from CloudKit API responses
  - Logs all CloudKit requests for debugging

Phase 3: Simplified authentication flow
  - Removed localStorage, cookies, property access strategies (didn't work)
  - Clean token promise with 10s timeout
  - Manual extraction instructions on failure

Phase 5: Debugging helpers
  - mistKitDebug.container() - Get CloudKit container
  - mistKitDebug.token() - Get current token
  - mistKitDebug.setToken(tok) - Manually set token
  - mistKitDebug.sendToServer() - Send token to server
  - mistKitDebug.inspectContainer() - Inspect container for token

TESTING STATUS UPDATE:
- Web auth token successfully extracted manually (158__54__... format verified)
- Implementation complete and ready for testing
- Blocked on CloudKit container configuration (421 Misdirected Request)
- Need to verify container setup at icloud.developer.apple.com/dashboard

FILES MODIFIED:
- Examples/MistDemo/Sources/MistDemo/Resources/index.html
- Examples/MistDemo/Sources/MistDemo/MistDemo.swift
- Examples/MistDemo/TESTING_STATUS.md

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants