field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

commit 7f642c39af0f265fbf4618fac14c893d21fbccef
parent 8472636ff9c8014e2beb6b4a9f4353312795255f
Author: triesap <tyson@radroots.org>
Date:   Thu, 10 Sep 2026 18:43:26 +0000

compose: Retain local media without network

- Keep local photo intake independent of service reachability
- Preserve incomplete descriptions through durable composer Save
- Verify sanitized bytes, strict rejection and actual runtime relaunch
- Qualify exact artifacts and explicit photo publication test input

Diffstat:
MTera.xcodeproj/project.pbxproj | 8++++++++
MTera/Runtime/TeraAddMediaCoordinator.swift | 2+-
MTera/State/TeraAddStore.swift | 4+---
MTeraFFI/provenance.json | 14+++++++-------
MTeraFFI/source.lock | 4++--
MTeraFFI/source/aarch64-apple-darwin.json | 8++++----
MTeraFFI/source/aarch64-apple-ios-sim.json | 8++++----
MTeraFFI/source/aarch64-apple-ios.json | 8++++----
ATeraTests/TeraOfflineMediaTests.swift | 264+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATeraUITests/TeraPhotoQualificationInput.swift | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MTeraUITests/TeraRemoteQualificationUITests.swift | 86+++++++------------------------------------------------------------------------
Mproject.yml | 2++
Mrelease/provenance.json | 6+++---
Mtest-fixtures/legacy-identifiers.v1.json | 96++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mtest-fixtures/maintainability-baseline.v1.json | 4++--
Mtest-fixtures/swiftlint-maintainability-baseline.v1.json | 4++--
16 files changed, 494 insertions(+), 116 deletions(-)

diff --git a/Tera.xcodeproj/project.pbxproj b/Tera.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 1D45B3D566A0A60063190854 /* TeraApp in Frameworks */ = {isa = PBXBuildFile; productRef = E666175A800272732A27CBEB /* TeraApp */; }; 229C9AC991E67663DA53D339 /* TeraRuntimeBoundedTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4FFC0023C620B68D0EC0ED /* TeraRuntimeBoundedTaskTests.swift */; }; 2479571FA99A7962794999C4 /* TeraSupportingStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0636AEDEB50DDDD328374A9 /* TeraSupportingStoreTests.swift */; }; + 25CCFD77EFA43657C473ADDC /* TeraPhotoQualificationInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD65DE234C7DD80CE3CB91EB /* TeraPhotoQualificationInput.swift */; }; 295A19317FC58A73A68C98DC /* TeraComposerPersistenceFFITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5938176A92F96A30CC39DFB /* TeraComposerPersistenceFFITests.swift */; }; 2A68D8FEEC3B88D47D696589 /* TeraOpenedMediaCloseFixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FCF1C4A8A7137623BBE223 /* TeraOpenedMediaCloseFixture.swift */; }; 2AEAFDD0EECA3B7F799B1287 /* TeraScopedAddTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A756C16ACFABBD36C48DF62 /* TeraScopedAddTests.swift */; }; @@ -25,6 +26,7 @@ 38EF7FA8874E82B28110A232 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 6967B3EAC4CF1A410B26DD1A /* PrivacyInfo.xcprivacy */; }; 39B47EE698E9D7CC716A372F /* TeraRuntimeShutdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E82E561F73A1BE6BBF09B7 /* TeraRuntimeShutdownTests.swift */; }; 3D219E759CFB685936EB7068 /* TeraCalendarEditingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C48B4B5442B1A404C1A328 /* TeraCalendarEditingTests.swift */; }; + 46ECA0EA5CE33D30BB5E08AF /* TeraOfflineMediaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649F88AE533C3BEEBC466894 /* TeraOfflineMediaTests.swift */; }; 4734A842C366608EC1BD31C5 /* TeraUserMessagesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46DDEE781FDD92A292FD847B /* TeraUserMessagesTests.swift */; }; 4B093458FAF4C6E4860E313A /* TeraComposerAutosaveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55E47B15C42F13ABF2DFF27 /* TeraComposerAutosaveTests.swift */; }; 53651EBAF42E8698717CE936 /* TeraScopedStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF28A9D711806A07A4A65EA8 /* TeraScopedStoreTests.swift */; }; @@ -137,6 +139,7 @@ 5EB21C33D6173E4FED05FDA0 /* TeraTodayReconciliationTaskTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraTodayReconciliationTaskTests.swift; sourceTree = "<group>"; }; 61B6F0B16BFD8F046E9C163F /* TeraMediaStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraMediaStoreTests.swift; sourceTree = "<group>"; }; 61D8C0835D5A1D5C36D80020 /* TeraBackgroundUploadFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraBackgroundUploadFixtures.swift; sourceTree = "<group>"; }; + 649F88AE533C3BEEBC466894 /* TeraOfflineMediaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraOfflineMediaTests.swift; sourceTree = "<group>"; }; 67ACCE25979BA86C1EC5BFD6 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; }; 6967B3EAC4CF1A410B26DD1A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 6B8677670E203FF68EC88983 /* TeraRootShellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRootShellTests.swift; sourceTree = "<group>"; }; @@ -192,6 +195,7 @@ F65B01F6F5F7E125148EC4FD /* TeraTodayReconciliationFFITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraTodayReconciliationFFITests.swift; sourceTree = "<group>"; }; FA1A8EA600F6BDC45333520C /* TeraCalendarComposerFFITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraCalendarComposerFFITests.swift; sourceTree = "<group>"; }; FB77C087F15C2F42CD959607 /* TeraRuntimeClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraRuntimeClientTests.swift; sourceTree = "<group>"; }; + FD65DE234C7DD80CE3CB91EB /* TeraPhotoQualificationInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraPhotoQualificationInput.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -228,6 +232,7 @@ isa = PBXGroup; children = ( 0D243145C276EBF17915AB4E /* TeraEditingProtectionUITests.swift */, + FD65DE234C7DD80CE3CB91EB /* TeraPhotoQualificationInput.swift */, 32CBDAE974FB8D7408108E69 /* TeraRemoteQualificationUITests.swift */, 5238B5F236C392C82CEF9838 /* TeraRootShellUITests.swift */, ); @@ -284,6 +289,7 @@ 27E1E87F9A11E85700F38635 /* TeraMediaVisibilityTests.swift */, 02F322CF98F8BEB908B93215 /* TeraMutationAdmissionTests.swift */, 21DD785439F415278214D746 /* TeraObservationCoalescingTests.swift */, + 649F88AE533C3BEEBC466894 /* TeraOfflineMediaTests.swift */, 74FCF1C4A8A7137623BBE223 /* TeraOpenedMediaCloseFixture.swift */, 9B10B1F3C7FCD9A72F04FC00 /* TeraOpenedMediaTests.swift */, 7AF60EF9DCFDC4ECE77AED67 /* TeraProductStartupTests.swift */, @@ -561,6 +567,7 @@ 5F252ABA8CA48D5E3A7A0157 /* TeraMediaVisibilityTests.swift in Sources */, BB71614D2A6017C071A39BED /* TeraMutationAdmissionTests.swift in Sources */, AC33144E09CB0384C4FB090C /* TeraObservationCoalescingTests.swift in Sources */, + 46ECA0EA5CE33D30BB5E08AF /* TeraOfflineMediaTests.swift in Sources */, 2A68D8FEEC3B88D47D696589 /* TeraOpenedMediaCloseFixture.swift in Sources */, 6FED2D2255CC702B33CDC760 /* TeraOpenedMediaTests.swift in Sources */, C46F8EB5B196FFDD37E0279A /* TeraProductStartupTests.swift in Sources */, @@ -610,6 +617,7 @@ buildActionMask = 2147483647; files = ( 6F09A1AF6E764AC0136E8F74 /* TeraEditingProtectionUITests.swift in Sources */, + 25CCFD77EFA43657C473ADDC /* TeraPhotoQualificationInput.swift in Sources */, C3177E827FE62639579C3EF5 /* TeraRemoteQualificationUITests.swift in Sources */, DF97F408D7D32F72C5AF5939 /* TeraRootShellUITests.swift in Sources */, ); diff --git a/Tera/Runtime/TeraAddMediaCoordinator.swift b/Tera/Runtime/TeraAddMediaCoordinator.swift @@ -280,7 +280,7 @@ actor TeraAddMediaCoordinator: TeraAddMediaHandling { byteSize: UInt64(prepared.file.sizeBytes), width: prepared.width, height: prepared.height, - alt: "Farm photo", + alt: "", preparedAtUnixSeconds: clock.unixSeconds() ) } diff --git a/Tera/State/TeraAddStore.swift b/Tera/State/TeraAddStore.swift @@ -172,7 +172,6 @@ final class TeraAddStore: ObservableObject { func checkPhotoService() async { guard !isCheckingBlossom, !Task.isCancelled else { return } guard blossomConfiguration != nil else { - mediaSupport = .unavailable message = "No photo service is configured for the current network profile." return } @@ -201,7 +200,6 @@ final class TeraAddStore: ObservableObject { serviceRequest == blossomGeneration else { return } guard await refreshBlossomSnapshot(), isCurrent(requestedGeneration), probe == probeGeneration else { return } - mediaSupport = .unavailable message = TeraAddPresentation.message(for: error) } } @@ -533,7 +531,7 @@ final class TeraAddStore: ObservableObject { } private func loadMediaSupport() async throws -> TeraAddMediaSupport { - guard blossomConfiguration != nil, let media else { return .unavailable } + guard let media else { return .unavailable } return try await media.support() } diff --git a/TeraFFI/provenance.json b/TeraFFI/provenance.json @@ -94,17 +94,17 @@ { "bytes": 78901, "path": "source/aarch64-apple-darwin.json", - "sha256": "69997c8e371b9ebc3f507e00c9435b26009b4b52106e84030c85cfaeaaa902db" + "sha256": "3e35363c2f01b2af4a76ae9800381f28c15b415e4b975586bd5a6809a53b03ce" }, { "bytes": 78745, "path": "source/aarch64-apple-ios-sim.json", - "sha256": "3a82420f0fe002714d9bb12c136a057cd0641d9fced919e99a353e4b870148e2" + "sha256": "857590f081e97b5445982b2155d88c9aeb2be3982904dc10c33fc491aa0f232f" }, { "bytes": 78741, "path": "source/aarch64-apple-ios.json", - "sha256": "3abb4bf8eefb6854fbae4f215a154fc36ed0aa0c443ce612056cb73a3e7938e0" + "sha256": "c598b77069dd1fc2baf0a0f3bcc6d88c3c00243c6238610726d5b0fd9de2668c" } ], "language": "swift", @@ -112,7 +112,7 @@ "schema": "radroots.artifact-manifest.v2", "source": { "repository": "https://github.com/radrootslabs/tera", - "tree": "eb9b39f231a0f34628b469926556a553c631efae" + "tree": "d2d70cf73831ad89f077bf5ff5158fa6bcf55638" }, "source_records": { "aarch64-apple-darwin": "source/aarch64-apple-darwin.json", @@ -176,17 +176,17 @@ { "bytes": 78901, "path": "TeraFFI/source/aarch64-apple-darwin.json", - "sha256": "69997c8e371b9ebc3f507e00c9435b26009b4b52106e84030c85cfaeaaa902db" + "sha256": "3e35363c2f01b2af4a76ae9800381f28c15b415e4b975586bd5a6809a53b03ce" }, { "bytes": 78745, "path": "TeraFFI/source/aarch64-apple-ios-sim.json", - "sha256": "3a82420f0fe002714d9bb12c136a057cd0641d9fced919e99a353e4b870148e2" + "sha256": "857590f081e97b5445982b2155d88c9aeb2be3982904dc10c33fc491aa0f232f" }, { "bytes": 78741, "path": "TeraFFI/source/aarch64-apple-ios.json", - "sha256": "3abb4bf8eefb6854fbae4f215a154fc36ed0aa0c443ce612056cb73a3e7938e0" + "sha256": "c598b77069dd1fc2baf0a0f3bcc6d88c3c00243c6238610726d5b0fd9de2668c" } ], "schema": "tera.installed-native-artifacts.v1" diff --git a/TeraFFI/source.lock b/TeraFFI/source.lock @@ -1,7 +1,7 @@ schema = "tera.installed-source.v1" repository = "https://github.com/radrootslabs/tera" -source_tree = "eb9b39f231a0f34628b469926556a553c631efae" -manifest_sha256 = "28a492cfc3ff2884083e46ae1662dd37fe95b4dde04d5cbbd12203532c003006" +source_tree = "d2d70cf73831ad89f077bf5ff5158fa6bcf55638" +manifest_sha256 = "3ec93a119b64fa97e6c6e293c34a7544e5286ee6a3cce24b90249cdf6f832101" source_date_epoch = 1787871027 [foundation] diff --git a/TeraFFI/source/aarch64-apple-darwin.json b/TeraFFI/source/aarch64-apple-darwin.json @@ -1695,13 +1695,13 @@ "sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d" }, "test-fixtures/legacy-identifiers.v1.json": { - "bytes": 123840, - "git_blob": "24b0e0ad27f7abd0a1c0449f9ac3474c3bbed1ff", + "bytes": 126032, + "git_blob": "c4533a5024e19bf1effaa9c16dd594a22203968d", "mode": "100644", - "sha256": "b753e2211bef28b5816044d2323799a7c42a051d503987aaadbb74d986d09fcb" + "sha256": "b283c8cb0a5bf254d38174ca83e96fb1711bc23c43c4bcb71c938a4fa6a15c32" } }, "policy": "staged_inputs", - "tree": "eb9b39f231a0f34628b469926556a553c631efae" + "tree": "d2d70cf73831ad89f077bf5ff5158fa6bcf55638" } } diff --git a/TeraFFI/source/aarch64-apple-ios-sim.json b/TeraFFI/source/aarch64-apple-ios-sim.json @@ -1691,13 +1691,13 @@ "sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d" }, "test-fixtures/legacy-identifiers.v1.json": { - "bytes": 123840, - "git_blob": "24b0e0ad27f7abd0a1c0449f9ac3474c3bbed1ff", + "bytes": 126032, + "git_blob": "c4533a5024e19bf1effaa9c16dd594a22203968d", "mode": "100644", - "sha256": "b753e2211bef28b5816044d2323799a7c42a051d503987aaadbb74d986d09fcb" + "sha256": "b283c8cb0a5bf254d38174ca83e96fb1711bc23c43c4bcb71c938a4fa6a15c32" } }, "policy": "staged_inputs", - "tree": "eb9b39f231a0f34628b469926556a553c631efae" + "tree": "d2d70cf73831ad89f077bf5ff5158fa6bcf55638" } } diff --git a/TeraFFI/source/aarch64-apple-ios.json b/TeraFFI/source/aarch64-apple-ios.json @@ -1691,13 +1691,13 @@ "sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d" }, "test-fixtures/legacy-identifiers.v1.json": { - "bytes": 123840, - "git_blob": "24b0e0ad27f7abd0a1c0449f9ac3474c3bbed1ff", + "bytes": 126032, + "git_blob": "c4533a5024e19bf1effaa9c16dd594a22203968d", "mode": "100644", - "sha256": "b753e2211bef28b5816044d2323799a7c42a051d503987aaadbb74d986d09fcb" + "sha256": "b283c8cb0a5bf254d38174ca83e96fb1711bc23c43c4bcb71c938a4fa6a15c32" } }, "policy": "staged_inputs", - "tree": "eb9b39f231a0f34628b469926556a553c631efae" + "tree": "d2d70cf73831ad89f077bf5ff5158fa6bcf55638" } } diff --git a/TeraTests/TeraOfflineMediaTests.swift b/TeraTests/TeraOfflineMediaTests.swift @@ -0,0 +1,264 @@ +import CryptoKit +import RadrootsKit +@testable import TeraApp +import UIKit +import XCTest + +@MainActor +final class TeraOfflineMediaTests: XCTestCase { + func testUnobservedServiceKeepsLocalImportAndIncompleteMetadataSaveAvailable() async throws { + let fixture = try OfflineMediaFixture() + defer { fixture.remove() } + let client = TeraRuntimeClient.production() + let signer = ComposerForbiddenSigner() + let configuration = fixture.configuration(signer) + let snapshot = try await client.start(configuration: configuration) + // A nil launch override selects the public default; it is not an uplink probe. + XCTAssertNotNil(snapshot.blossomConfiguration) + let store = TeraAddStore(runtimeClient: client, media: fixture.coordinator()) + store.configure(snapshot: snapshot) + await store.start() + store.selectType(.createPhotoUpdate) + XCTAssertEqual(store.mediaSupport, .init(library: true, camera: true)) + await store.importPhotos() + let photo = try XCTUnwrap(store.form.media.first) + XCTAssertNil(photo.remoteURL) + XCTAssertEqual(photo.alt, "") + let bytes = try Data(contentsOf: fixture.roots.stagedBlobsRoot.appendingPathComponent(photo.sha256)) + XCTAssertEqual(SHA256.hash(data: bytes).map { String(format: "%02x", $0) }.joined(), photo.sha256) + XCTAssertEqual(UInt64(bytes.count), photo.byteSize) + store.updateForm(\.content, " unfinished photo update ") + await store.save() + let saved = try XCTUnwrap(store.savedComposer) + XCTAssertEqual(saved.form.media.first?.alt, "") + XCTAssertEqual(saved.form.media.first?.opaqueReference, photo.opaqueReference) + await store.submit() + XCTAssertEqual(store.lastFailureCode, "invalid_media_reference") + XCTAssertNil(store.activeDraft) + let operations = try await client.legacyDraftPage() + XCTAssertTrue(operations.entries.isEmpty) + let afterSave = try await client.snapshot() + XCTAssertEqual(afterSave.blossomEvidence, snapshot.blossomEvidence) + store.stop() + _ = try await client.stop() + try await assertRelaunch(fixture, client: client, configuration: configuration, saved: saved) + let signs = await signer.requests + let transfers = await fixture.transfer.enqueueCount + XCTAssertEqual(signs, 0) + XCTAssertEqual(transfers, 0) + let requests = await fixture.picker.importRequests + XCTAssertEqual(requests.count, 1) + XCTAssertEqual(requests.first?.destinationScope, .cache) + XCTAssertEqual(requests.first?.selectionLimit, 20) + } + + func testAbsentServiceSnapshotKeepsLocalIntakeAndSaveWithoutProbing() async throws { + let backend = try TeraScopeBackend() + let original = await backend.value + let snapshot = TeraRuntimeSnapshot(identity: original.identity, relay: original.relay, + blossomConfiguration: nil, blossomEvidence: nil, + crateName: original.crateName, crateVersion: original.crateVersion, isClosed: false) + await backend.configure(snapshot) + let client = try await TeraScopeFixtures.client(backend) + let store = TeraAddStore(runtimeClient: client, media: AddMediaHarness()) + store.configure(snapshot: snapshot) + await store.start() + store.selectType(.createPhotoUpdate) + await store.checkPhotoService() + XCTAssertEqual(store.mediaSupport, .init(library: true, camera: true)) + await store.importPhotos() + let photo = try XCTUnwrap(store.form.media.first) + store.updateMediaAlt(id: photo.id, alt: "") + await store.save() + XCTAssertEqual(store.savedComposer?.form.media.first?.alt, "") + XCTAssertTrue(store.form.media.allSatisfy { $0.remoteURL == nil }) + let counts = await backend.counts + XCTAssertNil(counts[.probe]) + XCTAssertNil(counts[.save]) + store.stop() + _ = try await client.stop() + } + + func testFailedServiceProbeDoesNotDisablePickerOrPersistAnUploadURL() async throws { + let backend = try TeraScopeBackend() + let client = try await TeraScopeFixtures.client(backend) + let store = TeraAddStore(runtimeClient: client, media: AddMediaHarness()) + store.configure(snapshot: TeraScopeFixtures.snapshot()) + await store.start() + store.selectType(.createPhotoUpdate) + let failure = await backend.pause(.probe, fails: true) + let probe = Task { await store.checkPhotoService() } + await failure.entered.wait() + await failure.resume.open() + await probe.value + XCTAssertEqual(store.mediaSupport, .init(library: true, camera: true)) + XCTAssertNotNil(store.message) + await store.importPhotos() + let photo = try XCTUnwrap(store.form.media.first) + store.updateMediaAlt(id: photo.id, alt: " unfinished description\n") + await store.save() + XCTAssertEqual(store.savedComposer?.form.media.first?.alt, " unfinished description\n") + XCTAssertTrue(store.form.media.allSatisfy { $0.remoteURL == nil }) + let counts = await backend.counts + XCTAssertEqual(counts[.probe], 1) + XCTAssertNil(counts[.save]) + store.stop() + _ = try await client.stop() + } + + func testCameraIntakeRemainsLocalAndDeniedPermissionPreservesExistingEditing() async throws { + let fixture = try OfflineMediaFixture() + defer { fixture.remove() } + let backend = try TeraScopeBackend() + let client = try await TeraScopeFixtures.client(backend) + let store = TeraAddStore(runtimeClient: client, media: fixture.coordinator()) + store.configure(snapshot: TeraScopeFixtures.snapshot()) + await store.start() + store.selectType(.createPhotoUpdate) + await store.capturePhoto() + let photo = try XCTUnwrap(store.form.media.first) + XCTAssertNil(photo.remoteURL) + XCTAssertEqual(photo.alt, "") + let editing = store.form + await fixture.picker.denyPermission() + await store.importPhotos() + XCTAssertEqual(store.form, editing) + XCTAssertNotNil(store.message) + XCTAssertNil(store.activeDraft) + let requests = await fixture.picker.captureRequests + XCTAssertEqual(requests.count, 1) + XCTAssertEqual(requests.first?.destinationScope, .cache) + let transfers = await fixture.transfer.enqueueCount + XCTAssertEqual(transfers, 0) + store.stop() + _ = try await client.stop() + } + + func testUnavailablePickerIsNotEnabledByConfiguredOrReachableService() async throws { + let fixture = try OfflineMediaFixture() + defer { fixture.remove() } + await fixture.picker.setUnavailable() + let backend = try TeraScopeBackend() + let client = try await TeraScopeFixtures.client(backend) + let store = TeraAddStore(runtimeClient: client, media: fixture.coordinator()) + store.configure(snapshot: TeraScopeFixtures.snapshot()) + await store.start() + XCTAssertEqual(store.mediaSupport, .unavailable) + await store.checkPhotoService() + XCTAssertEqual(store.mediaSupport, .unavailable) + store.stop() + _ = try await client.stop() + } + + private func assertRelaunch(_ fixture: OfflineMediaFixture, client: TeraRuntimeClient, + configuration: TeraRuntimeLaunchConfiguration, saved: TeraComposerDraft) async throws + { + let snapshot = try await client.start(configuration: configuration) + let coordinator = fixture.coordinator() + let store = TeraAddStore(runtimeClient: client, media: coordinator) + store.configure(snapshot: snapshot) + await store.start() + let reopened = await store.reopenSaved(.composer(saved.id)) + XCTAssertTrue(reopened) + XCTAssertEqual(store.savedComposer, saved) + XCTAssertEqual(TeraComposerForm(editing: store.form), saved.form) + let opened = try await coordinator.open(store.form.media) + opened.close() + let photo = try XCTUnwrap(store.form.media.first) + store.updateMediaAlt(id: photo.id, alt: "Carrots prepared for the market") + await store.save() + XCTAssertEqual(store.savedComposer?.id, saved.id) + XCTAssertEqual(store.savedComposer?.revision, saved.revision + 1) + XCTAssertEqual(store.savedComposer?.form.media.first?.alt, "Carrots prepared for the market") + let loaded = try await client.loadComposer(scope: saved.scope, id: saved.id) + XCTAssertEqual(loaded, store.savedComposer) + store.stop() + _ = try await client.stop() + } +} + +@MainActor +private struct OfflineMediaFixture { + let runtime: MediaOwnershipFixture + let roots: RadrootsAppleFileRoots + let picker: OfflineMediaPicker + let transfer = BackgroundTransferHarness() + + init() throws { + runtime = try MediaOwnershipFixture() + roots = try RadrootsAppleFileRoots(appIdentifier: "test.offline-media", + dataRoot: runtime.root.appendingPathComponent("media/data"), + cacheRoot: runtime.root.appendingPathComponent("media/cache"), + temporaryRoot: runtime.root.appendingPathComponent("media/temporary")) + try FileManager.default.createDirectory(at: roots.cacheRoot, withIntermediateDirectories: true) + let bytes = UIGraphicsImageRenderer(size: CGSize(width: 2, height: 2)).pngData { context in + UIColor.green.setFill() + context.fill(CGRect(x: 0, y: 0, width: 2, height: 2)) + } + try bytes.write(to: roots.cacheRoot.appendingPathComponent("input.png")) + picker = OfflineMediaPicker(byteSize: UInt64(bytes.count)) + } + + func coordinator() -> TeraAddMediaCoordinator { + TeraAddMediaCoordinator(roots: roots, picker: picker, preparer: RadrootsAppleMediaPreparer(roots: roots), + transfer: transfer, clock: .fixed(unixSeconds: 1_800_000_000)) + } + + func configuration(_ signer: ComposerForbiddenSigner) -> TeraRuntimeLaunchConfiguration { + TeraRuntimeLaunchConfiguration( + applicationSupportDirectory: runtime.root.path, + publicKeyHex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + sourceGenerationHex: String(repeating: "04", count: 32), sourceGenerationCreatedAtUnixMilliseconds: 1_800_000_000_000, + protectedData: .available, networkProfile: .publicNetwork, writableRelays: [], blossom: nil, + app: TeraRuntimeAppMetadata(bundleIdentifier: "test.offline-media", version: "1", buildNumber: "1", buildSHA: nil), + signerGeneration: "offline-media-test", signer: signer, adoptBootstrapSettings: false + ) + } + + func remove() { + runtime.remove() + } +} + +private actor OfflineMediaPicker: RadrootsMediaPicker { + let byteSize: UInt64 + private var denied = false + private var available = true + private(set) var importRequests: [RadrootsMediaImportRequest] = [] + private(set) var captureRequests: [RadrootsMediaCaptureRequest] = [] + + init(byteSize: UInt64) { + self.byteSize = byteSize + } + + func denyPermission() { + denied = true + } + + func setUnavailable() { + available = false + } + + func currentSupport() throws -> RadrootsMediaPickerSupport { + try RadrootsMediaPickerSupport(importAvailable: available, cameraCaptureAvailable: available, + supportedImportKinds: [.image], supportedCaptureKinds: [.image], multipleSelectionSupported: true) + } + + func importMedia(_ request: RadrootsMediaImportRequest) throws -> RadrootsMediaImportResult { + importRequests.append(request) + return try RadrootsMediaImportResult(items: [asset(.libraryImport)]) + } + + func captureMedia(_ request: RadrootsMediaCaptureRequest) throws -> RadrootsMediaCaptureResult { + captureRequests.append(request) + return try RadrootsMediaCaptureResult(item: asset(.cameraCapture)) + } + + private func asset(_ source: RadrootsMediaSource) throws -> RadrootsMediaAsset { + guard !denied else { throw RadrootsCaptureIntakeError.permissionDenied } + guard available else { throw RadrootsCaptureIntakeError.unavailable } + return try RadrootsMediaAsset(source: source, kind: .image, file: RadrootsFileReference(scope: .cache, relativePath: "input.png"), + mediaType: "image/png", suggestedFilename: "input.png", sizeBytes: byteSize, + pixelWidth: 2, pixelHeight: 2, capturedAt: Date(timeIntervalSince1970: 1_800_000_000)) + } +} diff --git a/TeraUITests/TeraPhotoQualificationInput.swift b/TeraUITests/TeraPhotoQualificationInput.swift @@ -0,0 +1,92 @@ +import XCTest + +extension TeraRemoteQualificationUITests { + @MainActor + func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? { + guard let type = openAdd(app) else { + XCTFail("The Add bottom tab did not present the Add surface") + return nil + } + let newDraft = app.buttons["radroots.add.new"] + XCTAssertTrue(newDraft.waitForExistence(timeout: 10)) + newDraft.tap() + + guard selectPhotoUpdate(app, type: type) else { return nil } + let content = app.descendants(matching: .any)["radroots.add.content"] + XCTAssertTrue(content.waitForExistence(timeout: 10)) + guard waitUntilHittable(content, timeout: 10) else { + XCTFail("Photo update content did not become hittable") + return nil + } + content.tap() + content.typeText(marker) + let keyboardDone = app.buttons["radroots.add.keyboard.done"] + XCTAssertTrue(keyboardDone.waitForExistence(timeout: 10)) + keyboardDone.tap() + XCTAssertFalse(app.keyboards.firstMatch.waitForExistence(timeout: 5)) + + let library = app.descendants(matching: .any)["radroots.add.media.library"] + scrollTo(app, element: library) + guard library.waitForExistence(timeout: 10), waitUntilHittable(library, timeout: 10) else { + XCTFail("The Photo Library action did not become available through the visible UI") + return nil + } + library.tap() + let preparedStatus = app.staticTexts.matching( + NSPredicate(format: "label == 'Photo prepared. Add descriptive text before publishing.'") + ).firstMatch + guard preparedStatus.waitForExistence(timeout: 60) else { + XCTFail("The selected Photo update did not report the visible prepared state") + return nil + } + let prepared = app.descendants(matching: .any)["radroots.add.media.prepared"] + scrollTo(app, element: prepared) + guard prepared.waitForExistence(timeout: 10) else { + XCTFail("The selected Photo update did not reach the visible prepared state") + return nil + } + do { + try enterPhotoDescription(app) + } catch { + return nil + } + return readySubmit(app) + } + + @MainActor + private func selectPhotoUpdate(_ app: XCUIApplication, type: XCUIElement) -> Bool { + for _ in 0 ..< 3 where !app.buttons["Photo update"].exists { + type.tap() + _ = app.buttons["Photo update"].waitForExistence(timeout: 10) + } + let photoUpdate = app.buttons["Photo update"] + guard photoUpdate.exists else { + XCTFail("The Add type picker did not present Photo update") + return false + } + guard waitUntilHittable(photoUpdate, timeout: 10) else { + XCTFail("Photo update did not become hittable") + return false + } + + var selectedPhotoUpdate = false + for _ in 0 ..< 3 where !selectedPhotoUpdate { + photoUpdate.tap() + selectedPhotoUpdate = waitForValue(type, value: "Photo update", timeout: 10) + if !selectedPhotoUpdate, !photoUpdate.exists { + type.tap() + _ = photoUpdate.waitForExistence(timeout: 10) + } + } + guard selectedPhotoUpdate else { + XCTFail("Photo update selection did not update the Add composer type") + return false + } + return true + } + + @MainActor + func enterPhotoDescription(_ app: XCUIApplication) throws { + try enterText(app, identifier: "radroots.add.media.alt", value: "A green test image prepared for the photo update") + } +} diff --git a/TeraUITests/TeraRemoteQualificationUITests.swift b/TeraUITests/TeraRemoteQualificationUITests.swift @@ -750,7 +750,7 @@ final class TeraRemoteQualificationUITests: XCTestCase { } @MainActor - private func openAdd(_ app: XCUIApplication) -> XCUIElement? { + func openAdd(_ app: XCUIApplication) -> XCUIElement? { let add = app.tabBars.buttons["Add"] guard add.waitForExistence(timeout: 10) else { return nil } let type = app.descendants(matching: .any)["radroots.add.type"] @@ -764,79 +764,6 @@ final class TeraRemoteQualificationUITests: XCTestCase { } @MainActor - private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? { - guard let type = openAdd(app) else { - XCTFail("The Add bottom tab did not present the Add surface") - return nil - } - let newDraft = app.buttons["radroots.add.new"] - XCTAssertTrue(newDraft.waitForExistence(timeout: 10)) - newDraft.tap() - - for _ in 0 ..< 3 where !app.buttons["Photo update"].exists { - type.tap() - _ = app.buttons["Photo update"].waitForExistence(timeout: 10) - } - let photoUpdate = app.buttons["Photo update"] - guard photoUpdate.exists else { - XCTFail("The Add type picker did not present Photo update") - return nil - } - guard waitUntilHittable(photoUpdate, timeout: 10) else { - XCTFail("Photo update did not become hittable") - return nil - } - - let content = app.descendants(matching: .any)["radroots.add.content"] - var selectedPhotoUpdate = false - for _ in 0 ..< 3 where !selectedPhotoUpdate { - photoUpdate.tap() - selectedPhotoUpdate = waitForValue(type, value: "Photo update", timeout: 10) - if !selectedPhotoUpdate, !photoUpdate.exists { - type.tap() - _ = photoUpdate.waitForExistence(timeout: 10) - } - } - guard selectedPhotoUpdate else { - XCTFail("Photo update selection did not update the Add composer type") - return nil - } - XCTAssertTrue(content.waitForExistence(timeout: 10)) - guard waitUntilHittable(content, timeout: 10) else { - XCTFail("Photo update content did not become hittable") - return nil - } - content.tap() - content.typeText(marker) - let keyboardDone = app.buttons["radroots.add.keyboard.done"] - XCTAssertTrue(keyboardDone.waitForExistence(timeout: 10)) - keyboardDone.tap() - XCTAssertFalse(app.keyboards.firstMatch.waitForExistence(timeout: 5)) - - let library = app.descendants(matching: .any)["radroots.add.media.library"] - scrollTo(app, element: library) - guard library.waitForExistence(timeout: 10), waitUntilHittable(library, timeout: 10) else { - XCTFail("The Photo Library action did not become available through the visible UI") - return nil - } - library.tap() - let preparedStatus = app.staticTexts.matching( - NSPredicate(format: "label == 'Photo prepared. Add descriptive text before publishing.'") - ).firstMatch - guard preparedStatus.waitForExistence(timeout: 60) else { - XCTFail("The selected Photo update did not report the visible prepared state") - return nil - } - let prepared = app.descendants(matching: .any)["radroots.add.media.prepared"] - scrollTo(app, element: prepared) - guard prepared.waitForExistence(timeout: 10) else { - XCTFail("The selected Photo update did not reach the visible prepared state") - return nil - } - return readySubmit(app) - } - - @MainActor private func publishTextFlow( _ app: XCUIApplication, type: String, @@ -932,6 +859,7 @@ final class TeraRemoteQualificationUITests: XCTestCase { XCTFail("The governed prepared Photo update was obscured") throw QualificationError.missingProductSurface } + try enterPhotoDescription(app) case .event: try enterText(app, identifier: "radroots.add.title", value: marker) case .foodAvailability: @@ -1109,7 +1037,7 @@ final class TeraRemoteQualificationUITests: XCTestCase { } @MainActor - private func enterText( + func enterText( _ app: XCUIApplication, identifier: String, value: String @@ -1146,7 +1074,7 @@ final class TeraRemoteQualificationUITests: XCTestCase { } @MainActor - private func scrollTo(_ app: XCUIApplication, element: XCUIElement) { + func scrollTo(_ app: XCUIApplication, element: XCUIElement) { let root = app.descendants(matching: .any)["radroots.add.root"] let tabBar = app.tabBars.firstMatch for attempt in 0 ..< 16 where !isVisibleInAddViewport( @@ -1226,7 +1154,7 @@ final class TeraRemoteQualificationUITests: XCTestCase { } @MainActor - private func readySubmit(_ app: XCUIApplication) -> XCUIElement? { + func readySubmit(_ app: XCUIApplication) -> XCUIElement? { let submit = app.descendants(matching: .any)["radroots.add.submit"] let addRoot = app.descendants(matching: .any)["radroots.add.root"] for _ in 0 ..< 8 { @@ -1418,7 +1346,7 @@ final class TeraRemoteQualificationUITests: XCTestCase { } @MainActor - private func waitUntilHittable(_ element: XCUIElement, timeout: TimeInterval) -> Bool { + func waitUntilHittable(_ element: XCUIElement, timeout: TimeInterval) -> Bool { let predicate = NSPredicate(format: "exists == true AND hittable == true") let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element) return XCTWaiter.wait(for: [expectation], timeout: timeout) == .completed @@ -1461,7 +1389,7 @@ final class TeraRemoteQualificationUITests: XCTestCase { } @MainActor - private func waitForValue( + func waitForValue( _ element: XCUIElement, value: String, timeout: TimeInterval diff --git a/project.yml b/project.yml @@ -53,6 +53,7 @@ targets: - path: TeraTests/TeraEditingReplacementTests.swift - path: TeraTests/TeraEditingReplacementFFITests.swift - path: TeraTests/TeraEditingOperationProtectionTests.swift + - path: TeraTests/TeraOfflineMediaTests.swift - path: TeraTests/TeraComposerStartupTests.swift - path: TeraTests/TeraRuntimeInvalidationTests.swift - path: TeraTests/TeraRuntimeBackpressureTests.swift @@ -132,6 +133,7 @@ targets: sources: - path: TeraUITests/TeraRootShellUITests.swift - path: TeraUITests/TeraEditingProtectionUITests.swift + - path: TeraUITests/TeraPhotoQualificationInput.swift - path: TeraUITests/TeraRemoteQualificationUITests.swift - path: test-fixtures/local-social-personas.v1.json buildPhase: resources diff --git a/release/provenance.json b/release/provenance.json @@ -2,11 +2,11 @@ "artifacts": { "app_api_sha256": "020924097c0d7efc33128cb8fd3d3b2026d95f57c44da71880e585aff80f070b", "ffi_api_sha256": "472b6b7d854781438ff5f8dc526d22626996e3196bcb82697d195db83ec9802d", - "ffi_provenance_sha256": "28a492cfc3ff2884083e46ae1662dd37fe95b4dde04d5cbbd12203532c003006", + "ffi_provenance_sha256": "3ec93a119b64fa97e6c6e293c34a7544e5286ee6a3cce24b90249cdf6f832101", "info_plist_sha256": "15ef08b1cdd1096cfb9eeaf5be5bf8f814807a7ca9350bbbb47860fa72ec13ef", "privacy_manifest_sha256": "a331d51864743ebe4e00dd22360b4a538b6b3ac26a6b3eb54094e60a36959a12", "sbom_sha256": "61e3d99a2c7f051a91d6a38b3b1f7974fb4c56da3a1c9758f5ad06fc66f1d856", - "xcode_project_sha256": "c52fe9efc17252f36e4305e452d4796c13b3877f8a24765fc0d465e4bca89dcc" + "xcode_project_sha256": "7ad75c2109f739287070d67bb6c2e331e25f2479d9f0aaab372321e9f8460e1f" }, "disposition": "unsigned", "platforms": [ @@ -22,7 +22,7 @@ "lib_revision": "363b9be18831be2fd6ef505d6154fb878ed8efd7", "source_date_epoch": 1787871027, "swift_package_lock_sha256": "94ae067a374726cdaf6b4ca0a5e44663c57fcdc5334060c5ffef5e79cfbf04c0", - "tera_ffi_source_tree": "eb9b39f231a0f34628b469926556a553c631efae", + "tera_ffi_source_tree": "d2d70cf73831ad89f077bf5ff5158fa6bcf55638", "xcode_package_lock_sha256": "c7f41934ea25f7a287bdc4f3a6ecabbf09a3a0bdd0f5a3e58183f355ca814096" }, "version": "0.1.0-alpha" diff --git a/test-fixtures/legacy-identifiers.v1.json b/test-fixtures/legacy-identifiers.v1.json @@ -261,6 +261,10 @@ "count": 2 }, { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 2 + }, + { "path": "TeraTests/TeraRemoteQualificationTests.swift", "count": 1 }, @@ -365,6 +369,10 @@ { "path": "TeraTests/TeraBackgroundUploadFixtures.swift", "count": 1 + }, + { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 1 } ] }, @@ -713,6 +721,10 @@ "category": "shared_apple_api", "occurrences": [ { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 1 + }, + { "path": "TeraTests/TeraUserMessagesTests.swift", "count": 1 } @@ -741,6 +753,10 @@ "count": 2 }, { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 1 + }, + { "path": "TeraTests/TeraUserMessagesTests.swift", "count": 1 } @@ -863,6 +879,10 @@ "count": 5 }, { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 1 + }, + { "path": "TeraTests/TeraRemoteQualificationTests.swift", "count": 1 }, @@ -1087,6 +1107,10 @@ "count": 1 }, { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 1 + }, + { "path": "TeraTests/TeraRemoteQualificationTests.swift", "count": 1 }, @@ -1161,6 +1185,10 @@ { "path": "Tera/Runtime/TeraAddMediaCoordinator.swift", "count": 2 + }, + { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 2 } ] }, @@ -1175,6 +1203,10 @@ { "path": "TeraTests/TeraBackgroundUploadFixtures.swift", "count": 1 + }, + { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 2 } ] }, @@ -1189,6 +1221,10 @@ { "path": "TeraTests/TeraBackgroundUploadFixtures.swift", "count": 1 + }, + { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 2 } ] }, @@ -1203,6 +1239,10 @@ { "path": "TeraTests/TeraBackgroundUploadFixtures.swift", "count": 1 + }, + { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 2 } ] }, @@ -1217,6 +1257,10 @@ { "path": "TeraTests/TeraBackgroundUploadFixtures.swift", "count": 1 + }, + { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 2 } ] }, @@ -1231,6 +1275,10 @@ { "path": "TeraTests/TeraBackgroundUploadFixtures.swift", "count": 1 + }, + { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 1 } ] }, @@ -1245,6 +1293,10 @@ { "path": "TeraTests/TeraBackgroundUploadFixtures.swift", "count": 2 + }, + { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 2 } ] }, @@ -2085,8 +2137,12 @@ "count": 1 }, { + "path": "TeraUITests/TeraPhotoQualificationInput.swift", + "count": 1 + }, + { "path": "TeraUITests/TeraRemoteQualificationUITests.swift", - "count": 16 + "count": 15 } ] }, @@ -2221,8 +2277,12 @@ "count": 1 }, { + "path": "TeraUITests/TeraPhotoQualificationInput.swift", + "count": 1 + }, + { "path": "TeraUITests/TeraRemoteQualificationUITests.swift", - "count": 2 + "count": 1 } ] }, @@ -2257,6 +2317,10 @@ { "path": "Tera/Views/TeraAddView.swift", "count": 1 + }, + { + "path": "TeraUITests/TeraPhotoQualificationInput.swift", + "count": 1 } ] }, @@ -2283,8 +2347,12 @@ "count": 1 }, { + "path": "TeraUITests/TeraPhotoQualificationInput.swift", + "count": 1 + }, + { "path": "TeraUITests/TeraRemoteQualificationUITests.swift", - "count": 3 + "count": 2 } ] }, @@ -2297,8 +2365,12 @@ "count": 1 }, { + "path": "TeraUITests/TeraPhotoQualificationInput.swift", + "count": 1 + }, + { "path": "TeraUITests/TeraRemoteQualificationUITests.swift", - "count": 2 + "count": 1 } ] }, @@ -2311,8 +2383,12 @@ "count": 1 }, { + "path": "TeraUITests/TeraPhotoQualificationInput.swift", + "count": 1 + }, + { "path": "TeraUITests/TeraRemoteQualificationUITests.swift", - "count": 2 + "count": 1 } ] }, @@ -4853,6 +4929,16 @@ "count": 2 } ] + }, + { + "identifier": "RadrootsMediaSource", + "category": "shared_apple_api", + "occurrences": [ + { + "path": "TeraTests/TeraOfflineMediaTests.swift", + "count": 1 + } + ] } ] } diff --git a/test-fixtures/maintainability-baseline.v1.json b/test-fixtures/maintainability-baseline.v1.json @@ -11,12 +11,12 @@ {"path": "Tera/Runtime/TeraGeneratedRuntimeBackend.swift", "maximum_lines": 1474}, {"path": "Tera/Runtime/TeraRuntimeClient.swift", "maximum_lines": 820}, {"path": "Tera/Runtime/TeraRuntimeModels.swift", "maximum_lines": 1154}, - {"path": "Tera/State/TeraAddStore.swift", "maximum_lines": 785}, + {"path": "Tera/State/TeraAddStore.swift", "maximum_lines": 783}, {"path": "Tera/State/TeraConfigurationStore.swift", "maximum_lines": 759}, {"path": "Tera/Views/TeraSupportingViews.swift", "maximum_lines": 629}, {"path": "TeraTests/TeraAddStoreTests.swift", "maximum_lines": 1366}, {"path": "TeraTests/TeraStateMigrationTests.swift", "maximum_lines": 718}, - {"path": "TeraUITests/TeraRemoteQualificationUITests.swift", "maximum_lines": 2212} + {"path": "TeraUITests/TeraRemoteQualificationUITests.swift", "maximum_lines": 2140} ], "python_file_exception": [ {"path": "scripts/local-social-fixture.py", "maximum_lines": 2626}, diff --git a/test-fixtures/swiftlint-maintainability-baseline.v1.json b/test-fixtures/swiftlint-maintainability-baseline.v1.json @@ -1 +1 @@ -[{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"warning","reason":"Initializer body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines","location":{"character":3,"file":"Tera\/App\/TeraAppModel.swift","line":21}},"text":" init("},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"warning","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 330 lines","location":{"character":1,"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","line":51}},"text":"actor TeraAddMediaCoordinator: TeraAddMediaHandling {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 458","location":{"character":1,"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","line":458}},"text":"#endif"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 467 lines","location":{"character":15,"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","line":111}},"text":"private final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"error","reason":"File should contain 1000 lines or less: currently contains 1474","location":{"character":1,"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","line":1474}},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 739 lines","location":{"character":1,"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":3}},"text":"actor TeraRuntimeClient {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 70 lines","location":{"character":3,"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":379}},"text":" func changes(bufferCapacity: Int = 16) async throws -> AsyncStream<TeraRuntimeChange> {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 820","location":{"character":1,"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":820}},"text":"}"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"error","reason":"File should contain 1000 lines or less: currently contains 1154","location":{"character":1,"file":"Tera\/Runtime\/TeraRuntimeModels.swift","line":1154}},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 600 lines","location":{"character":7,"file":"Tera\/State\/TeraAddStore.swift","line":4}},"text":"final class TeraAddStore: ObservableObject {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 785","location":{"character":1,"file":"Tera\/State\/TeraAddStore.swift","line":785}},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 468 lines","location":{"character":1,"file":"Tera\/State\/TeraConfigurationStore.swift","line":78}},"text":"actor TeraConfigurationStore {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"error","reason":"Function body should span 100 lines or less excluding comments and whitespace: currently spans 109 lines","location":{"character":5,"file":"Tera\/State\/TeraConfigurationStore.swift","line":148}},"text":" func load() throws -> TeraAppConfiguration {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 759","location":{"character":1,"file":"Tera\/State\/TeraConfigurationStore.swift","line":759}},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 390 lines","location":{"character":1,"file":"Tera\/State\/TeraSessionStore.swift","line":39}},"text":"actor TeraSessionStore {"},{"violation":{"ruleDescription":"Complexity of function bodies should be limited.","ruleName":"Cyclomatic Complexity","ruleIdentifier":"cyclomatic_complexity","severity":"warning","reason":"Function should have complexity 10 or less; currently complexity is 13","location":{"character":11,"file":"Tera\/State\/TeraSessionStore.swift","line":125}},"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 452","location":{"character":1,"file":"Tera\/State\/TeraSessionStore.swift","line":452}},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"error","reason":"Struct body should span 350 lines or less excluding comments and whitespace: currently spans 375 lines","location":{"character":1,"file":"Tera\/Views\/TeraAddView.swift","line":4}},"text":"struct TeraAddView: View {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 407","location":{"character":1,"file":"Tera\/Views\/TeraAddView.swift","line":407}},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"warning","reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 259 lines","location":{"character":1,"file":"Tera\/Views\/TeraSupportingViews.swift","line":365}},"text":"struct TeraSettingsView: View {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 629","location":{"character":1,"file":"Tera\/Views\/TeraSupportingViews.swift","line":629}},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 664 lines","location":{"character":7,"file":"TeraTests\/TeraAddStoreTests.swift","line":6}},"text":"final class TeraAddStoreTests: XCTestCase {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 489 lines","location":{"character":1,"file":"TeraTests\/TeraAddStoreTests.swift","line":759}},"text":"actor AddBackend: TeraRuntimeBackend {"},{"violation":{"ruleDescription":"Number of function parameters should be low.","ruleName":"Function Parameter Count","ruleIdentifier":"function_parameter_count","severity":"warning","reason":"Function should have 5 parameters or less: it currently has 8","location":{"character":11,"file":"TeraTests\/TeraAddStoreTests.swift","line":1196}},"text":" private func makeStatus("},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"error","reason":"File should contain 1000 lines or less: currently contains 1366","location":{"character":1,"file":"TeraTests\/TeraAddStoreTests.swift","line":1366}},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"warning","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 307 lines","location":{"character":7,"file":"TeraTests\/TeraRemoteQualificationTests.swift","line":7}},"text":"final class TeraRemoteQualificationTests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 94 lines","location":{"character":3,"file":"TeraTests\/TeraRemoteQualificationTests.swift","line":45}},"text":" func testQualificationRejectsAmbiguousOrUnsafeHarnessValues() {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","location":{"character":3,"file":"TeraTests\/TeraRemoteQualificationTests.swift","line":212}},"text":" func testAuthorizationEvidenceIsRedactedEphemeralAndRelaunchCleaned() throws {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"warning","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 298 lines","location":{"character":7,"file":"TeraTests\/TeraRuntimeClientTests.swift","line":4}},"text":"final class TeraRuntimeClientTests: XCTestCase {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 580 lines","location":{"character":7,"file":"TeraTests\/TeraStateMigrationTests.swift","line":6}},"text":"final class TeraStateMigrationTests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","location":{"character":5,"file":"TeraTests\/TeraStateMigrationTests.swift","line":7}},"text":" func testRelayValidationMatchesRuntimeProfiles() throws {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 622","location":{"character":1,"file":"TeraTests\/TeraStateMigrationTests.swift","line":622}},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"warning","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 252 lines","location":{"character":9,"file":"TeraTests\/TeraSupportingStoreTests.swift","line":207}},"text":"private actor SupportingBackend: TeraRuntimeBackend {"},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 486","location":{"character":1,"file":"TeraTests\/TeraSupportingStoreTests.swift","line":486}},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 1493 lines","location":{"character":7,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":4}},"text":"final class TeraRemoteQualificationUITests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 73 lines","location":{"character":3,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":62}},"text":" func testLocalSocialFiveFlowScenario() throws {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","location":{"character":3,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":174}},"text":" func testLocalSocialDeterministicPersonas() throws {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 54 lines","location":{"character":3,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":247}},"text":" func testRemoteBlossomUploadAndRecovery() throws {"},{"violation":{"ruleDescription":"Complexity of function bodies should be limited.","ruleName":"Cyclomatic Complexity","ruleIdentifier":"cyclomatic_complexity","severity":"warning","reason":"Function should have complexity 10 or less; currently complexity is 18","location":{"character":11,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":573}},"text":" private func expectedAddFieldLabel(_ identifier: String, type: String) -> String {"},{"violation":{"ruleDescription":"Complexity of function bodies should be limited.","ruleName":"Cyclomatic Complexity","ruleIdentifier":"cyclomatic_complexity","severity":"warning","reason":"Function should have complexity 10 or less; currently complexity is 11","location":{"character":11,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":767}},"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","location":{"character":11,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":767}},"text":" private func preparePhotoUpdate(_ app: XCUIApplication, marker: String) -> XCUIElement? {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","ruleIdentifier":"function_body_length","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 67 lines","location":{"character":10,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":1736}},"text":" static func environment("},{"violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","ruleIdentifier":"file_length","severity":"error","reason":"File should contain 1000 lines or less: currently contains 2212","location":{"character":1,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":2212}},"text":"}"}] -\ No newline at end of file +[{"text":" init(","violation":{"ruleName":"Function Body Length","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","severity":"warning","location":{"line":21,"character":3,"file":"Tera\/App\/TeraAppModel.swift"},"reason":"Initializer body should span 50 lines or less excluding comments and whitespace: currently spans 52 lines"}},{"text":"actor TeraAddMediaCoordinator: TeraAddMediaHandling {","violation":{"ruleIdentifier":"type_body_length","ruleName":"Type Body Length","severity":"warning","location":{"character":1,"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift","line":51},"reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 330 lines","ruleDescription":"Type bodies should not span too many lines"}},{"text":"#endif","violation":{"location":{"line":458,"character":1,"file":"Tera\/Runtime\/TeraAddMediaCoordinator.swift"},"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 458","severity":"warning","ruleIdentifier":"file_length"}},{"text":"private final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {","violation":{"severity":"error","ruleName":"Type Body Length","location":{"line":111,"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","character":15},"ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 467 lines"}},{"text":"}","violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","severity":"error","ruleIdentifier":"file_length","location":{"file":"Tera\/Runtime\/TeraGeneratedRuntimeBackend.swift","line":1474,"character":1},"reason":"File should contain 1000 lines or less: currently contains 1474"}},{"text":"actor TeraRuntimeClient {","violation":{"location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","character":1,"line":3},"reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 739 lines","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","severity":"error"}},{"text":" func changes(bufferCapacity: Int = 16) async throws -> AsyncStream<TeraRuntimeChange> {","violation":{"ruleName":"Function Body Length","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 70 lines","ruleDescription":"Function bodies should not span too many lines","location":{"file":"Tera\/Runtime\/TeraRuntimeClient.swift","line":379,"character":3},"severity":"warning","ruleIdentifier":"function_body_length"}},{"violation":{"severity":"warning","ruleDescription":"Files should not span too many lines.","location":{"character":1,"line":820,"file":"Tera\/Runtime\/TeraRuntimeClient.swift"},"ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 820","ruleIdentifier":"file_length"},"text":"}"},{"violation":{"ruleName":"File Length","severity":"error","location":{"line":1154,"character":1,"file":"Tera\/Runtime\/TeraRuntimeModels.swift"},"reason":"File should contain 1000 lines or less: currently contains 1154","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines."},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleIdentifier":"type_body_length","location":{"file":"Tera\/State\/TeraAddStore.swift","character":7,"line":4},"reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 598 lines","severity":"error","ruleName":"Type Body Length"},"text":"final class TeraAddStore: ObservableObject {"},{"violation":{"ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"line":783,"file":"Tera\/State\/TeraAddStore.swift","character":1},"ruleName":"File Length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 783"},"text":"}"},{"violation":{"location":{"character":1,"file":"Tera\/State\/TeraConfigurationStore.swift","line":78},"ruleName":"Type Body Length","severity":"error","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 468 lines","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines"},"text":"actor TeraConfigurationStore {"},{"violation":{"ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","location":{"file":"Tera\/State\/TeraConfigurationStore.swift","line":148,"character":5},"severity":"error","reason":"Function body should span 100 lines or less excluding comments and whitespace: currently spans 109 lines"},"text":" func load() throws -> TeraAppConfiguration {"},{"violation":{"reason":"File should contain 400 lines or less: currently contains 759","ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length","severity":"warning","location":{"character":1,"line":759,"file":"Tera\/State\/TeraConfigurationStore.swift"}},"text":"}"},{"violation":{"reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 390 lines","severity":"error","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"character":1,"file":"Tera\/State\/TeraSessionStore.swift","line":39}},"text":"actor TeraSessionStore {"},{"violation":{"reason":"Function should have complexity 10 or less; currently complexity is 13","ruleDescription":"Complexity of function bodies should be limited.","ruleIdentifier":"cyclomatic_complexity","ruleName":"Cyclomatic Complexity","severity":"warning","location":{"character":11,"line":125,"file":"Tera\/State\/TeraSessionStore.swift"}},"text":" private func start(acceptingReconfiguration: Bool) async -> TeraSessionPhase {"},{"violation":{"ruleName":"File Length","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","location":{"character":1,"file":"Tera\/State\/TeraSessionStore.swift","line":452},"severity":"warning","reason":"File should contain 400 lines or less: currently contains 452"},"text":"}"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","severity":"error","ruleIdentifier":"type_body_length","reason":"Struct body should span 350 lines or less excluding comments and whitespace: currently spans 375 lines","location":{"character":1,"file":"Tera\/Views\/TeraAddView.swift","line":4}},"text":"struct TeraAddView: View {"},{"violation":{"reason":"File should contain 400 lines or less: currently contains 407","severity":"warning","ruleDescription":"Files should not span too many lines.","location":{"line":407,"file":"Tera\/Views\/TeraAddView.swift","character":1},"ruleIdentifier":"file_length","ruleName":"File Length"},"text":"}"},{"violation":{"location":{"character":1,"file":"Tera\/Views\/TeraSupportingViews.swift","line":365},"reason":"Struct body should span 250 lines or less excluding comments and whitespace: currently spans 259 lines","ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","ruleIdentifier":"type_body_length","severity":"warning"},"text":"struct TeraSettingsView: View {"},{"violation":{"severity":"warning","ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","location":{"line":629,"character":1,"file":"Tera\/Views\/TeraSupportingViews.swift"},"reason":"File should contain 400 lines or less: currently contains 629","ruleName":"File Length"},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 664 lines","severity":"error","ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":6,"character":7}},"text":"final class TeraAddStoreTests: XCTestCase {"},{"violation":{"ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length","reason":"Actor body should span 350 lines or less excluding comments and whitespace: currently spans 489 lines","ruleIdentifier":"type_body_length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":759,"character":1},"severity":"error"},"text":"actor AddBackend: TeraRuntimeBackend {"},{"violation":{"ruleIdentifier":"function_parameter_count","location":{"character":11,"file":"TeraTests\/TeraAddStoreTests.swift","line":1196},"ruleDescription":"Number of function parameters should be low.","ruleName":"Function Parameter Count","reason":"Function should have 5 parameters or less: it currently has 8","severity":"warning"},"text":" private func makeStatus("},{"violation":{"severity":"error","ruleDescription":"Files should not span too many lines.","ruleName":"File Length","reason":"File should contain 1000 lines or less: currently contains 1366","ruleIdentifier":"file_length","location":{"file":"TeraTests\/TeraAddStoreTests.swift","line":1366,"character":1}},"text":"}"},{"violation":{"ruleIdentifier":"type_body_length","location":{"line":7,"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":7},"severity":"warning","reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 307 lines","ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length"},"text":"final class TeraRemoteQualificationTests: XCTestCase {"},{"violation":{"ruleDescription":"Function bodies should not span too many lines","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 94 lines","severity":"warning","location":{"line":45,"file":"TeraTests\/TeraRemoteQualificationTests.swift","character":3},"ruleIdentifier":"function_body_length","ruleName":"Function Body Length"},"text":" func testQualificationRejectsAmbiguousOrUnsafeHarnessValues() {"},{"violation":{"severity":"warning","ruleDescription":"Function bodies should not span too many lines","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 53 lines","ruleIdentifier":"function_body_length","location":{"character":3,"line":212,"file":"TeraTests\/TeraRemoteQualificationTests.swift"},"ruleName":"Function Body Length"},"text":" func testAuthorizationEvidenceIsRedactedEphemeralAndRelaunchCleaned() throws {"},{"violation":{"severity":"warning","location":{"character":7,"line":4,"file":"TeraTests\/TeraRuntimeClientTests.swift"},"reason":"Class body should span 250 lines or less excluding comments and whitespace: currently spans 298 lines","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","ruleName":"Type Body Length"},"text":"final class TeraRuntimeClientTests: XCTestCase {"},{"violation":{"reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 580 lines","ruleIdentifier":"type_body_length","severity":"error","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","line":6,"character":7},"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines"},"text":"final class TeraStateMigrationTests: XCTestCase {"},{"violation":{"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 61 lines","severity":"warning","location":{"line":7,"character":5,"file":"TeraTests\/TeraStateMigrationTests.swift"},"ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length"},"text":" func testRelayValidationMatchesRuntimeProfiles() throws {"},{"text":"}","violation":{"ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines.","ruleName":"File Length","severity":"warning","reason":"File should contain 400 lines or less: currently contains 622","location":{"file":"TeraTests\/TeraStateMigrationTests.swift","character":1,"line":622}}},{"text":"private actor SupportingBackend: TeraRuntimeBackend {","violation":{"ruleName":"Type Body Length","ruleDescription":"Type bodies should not span too many lines","reason":"Actor body should span 250 lines or less excluding comments and whitespace: currently spans 252 lines","location":{"line":207,"character":9,"file":"TeraTests\/TeraSupportingStoreTests.swift"},"ruleIdentifier":"type_body_length","severity":"warning"}},{"text":"}","violation":{"severity":"warning","location":{"file":"TeraTests\/TeraSupportingStoreTests.swift","character":1,"line":486},"ruleName":"File Length","reason":"File should contain 400 lines or less: currently contains 486","ruleIdentifier":"file_length","ruleDescription":"Files should not span too many lines."}},{"text":"final class TeraRemoteQualificationUITests: XCTestCase {","violation":{"ruleName":"Type Body Length","severity":"error","reason":"Class body should span 350 lines or less excluding comments and whitespace: currently spans 1425 lines","ruleIdentifier":"type_body_length","ruleDescription":"Type bodies should not span too many lines","location":{"character":7,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":4}}},{"text":" func testLocalSocialFiveFlowScenario() throws {","violation":{"ruleName":"Function Body Length","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":62,"character":3},"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 73 lines","ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length","severity":"warning"}},{"text":" func testLocalSocialDeterministicPersonas() throws {","violation":{"ruleDescription":"Function bodies should not span too many lines","severity":"warning","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 66 lines","location":{"character":3,"line":174,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift"},"ruleName":"Function Body Length","ruleIdentifier":"function_body_length"}},{"text":" func testRemoteBlossomUploadAndRecovery() throws {","violation":{"ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 54 lines","ruleName":"Function Body Length","severity":"warning","location":{"line":247,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":3}}},{"text":" private func expectedAddFieldLabel(_ identifier: String, type: String) -> String {","violation":{"ruleIdentifier":"cyclomatic_complexity","ruleDescription":"Complexity of function bodies should be limited.","ruleName":"Cyclomatic Complexity","severity":"warning","location":{"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","line":573,"character":11},"reason":"Function should have complexity 10 or less; currently complexity is 18"}},{"text":" static func environment(","violation":{"ruleName":"Function Body Length","severity":"warning","location":{"line":1664,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":10},"reason":"Function body should span 50 lines or less excluding comments and whitespace: currently spans 67 lines","ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines"}},{"text":"}","violation":{"ruleName":"File Length","severity":"error","ruleDescription":"Files should not span too many lines.","reason":"File should contain 1000 lines or less: currently contains 2140","ruleIdentifier":"file_length","location":{"line":2140,"file":"TeraUITests\/TeraRemoteQualificationUITests.swift","character":1}}}] +\ No newline at end of file