mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-07 11:19:58 +02:00
feat(apple): send files to a remembered device
Adds the Send files action to the device detail, routing the picked selection through sendToContact. Rather than a second picker path, sharePickedFiles now takes a ShareDestination, so the macOS security-scoped access handling covers both routes. A contact destination carries no access policy, matching the core's rule that an offer share is never public.
This commit is contained in:
@@ -22,7 +22,8 @@ final class ContactsModelTests: XCTestCase {
|
||||
let model = ContactsModel(
|
||||
repository: gateway,
|
||||
messages: UiMessageController(),
|
||||
preferences: preferences
|
||||
preferences: preferences,
|
||||
fileSystemService: FakeFileSystemService()
|
||||
)
|
||||
return (model, preferences)
|
||||
}
|
||||
@@ -213,6 +214,68 @@ final class ContactsModelTests: XCTestCase {
|
||||
XCTAssertEqual(subject.displayName, "Laptop")
|
||||
}
|
||||
|
||||
/// Files picked for a device go out as an offer, never as an invitation
|
||||
/// anyone holding the ticket could use.
|
||||
func testSendingToAContactUsesTheContactDestination() async {
|
||||
let gateway = FakeCoreGateway()
|
||||
let files = FakeFileSystemService()
|
||||
let defaults = UserDefaults(suiteName: "contacts-send-\(UUID().uuidString)")!
|
||||
let preferences = AppPreferencesRepository(
|
||||
defaults: defaults,
|
||||
fallback: AppPreferencesDefaults(
|
||||
username: "tester",
|
||||
receiveFolder: ReceiveFolder(kind: .fileSystemPath, value: "/tmp", displayName: "Downloads"),
|
||||
themeMode: .system
|
||||
)
|
||||
)
|
||||
let model = ContactsModel(
|
||||
repository: gateway,
|
||||
messages: UiMessageController(),
|
||||
preferences: preferences,
|
||||
fileSystemService: files
|
||||
)
|
||||
gateway.sendToContactResult = .success(
|
||||
Share(transferId: 1, ticket: "vnd1:x", transferName: "doc", contentHash: "h", fileCount: 1, totalSize: 2)
|
||||
)
|
||||
|
||||
model.chooseFilesToSend(to: "peer")
|
||||
XCTAssertTrue(model.pendingFilePick)
|
||||
await model.onFilesPicked([
|
||||
PickedShareFile(value: "/tmp/doc.txt", displayName: "doc.txt", isDirectory: false)
|
||||
])
|
||||
|
||||
XCTAssertEqual(files.shareDestinations, [.contact(endpointId: "peer")])
|
||||
XCTAssertEqual(gateway.sentToContacts, ["peer"])
|
||||
}
|
||||
|
||||
/// A pick that arrives with no target must not be sent anywhere.
|
||||
func testPickedFilesWithoutATargetAreIgnored() async {
|
||||
let gateway = FakeCoreGateway()
|
||||
let files = FakeFileSystemService()
|
||||
let defaults = UserDefaults(suiteName: "contacts-send-\(UUID().uuidString)")!
|
||||
let preferences = AppPreferencesRepository(
|
||||
defaults: defaults,
|
||||
fallback: AppPreferencesDefaults(
|
||||
username: "tester",
|
||||
receiveFolder: ReceiveFolder(kind: .fileSystemPath, value: "/tmp", displayName: "Downloads"),
|
||||
themeMode: .system
|
||||
)
|
||||
)
|
||||
let model = ContactsModel(
|
||||
repository: gateway,
|
||||
messages: UiMessageController(),
|
||||
preferences: preferences,
|
||||
fileSystemService: files
|
||||
)
|
||||
|
||||
await model.onFilesPicked([
|
||||
PickedShareFile(value: "/tmp/doc.txt", displayName: "doc.txt", isDirectory: false)
|
||||
])
|
||||
|
||||
XCTAssertTrue(files.shareDestinations.isEmpty)
|
||||
XCTAssertTrue(gateway.sentToContacts.isEmpty)
|
||||
}
|
||||
|
||||
func testUnreachableContactIsSurfacedForRepairing() async {
|
||||
let gateway = FakeCoreGateway()
|
||||
gateway.contactsResult = .success([contact("peer", canSend: false)])
|
||||
@@ -247,7 +310,8 @@ final class PairingSuggestionTests: XCTestCase {
|
||||
let model = ContactsModel(
|
||||
repository: gateway,
|
||||
messages: UiMessageController(),
|
||||
preferences: preferences
|
||||
preferences: preferences,
|
||||
fileSystemService: FakeFileSystemService()
|
||||
)
|
||||
return (model, preferences)
|
||||
}
|
||||
|
||||
@@ -166,8 +166,20 @@ final class FakeFileSystemService: FileSystemService {
|
||||
func defaultReceiveFolder() -> ReceiveFolder { folder }
|
||||
func validateReceiveFolder(_ folder: ReceiveFolder) async -> FolderAccessStatus { .writable }
|
||||
func canRevealReceiveFolder(_ folder: ReceiveFolder) -> Bool { false }
|
||||
func sharePickedFiles(repository: CoreGateway, files: [PickedShareFile], transferName: String, senderName: String, accessPolicy: ShareAccessPolicy) async -> Result<Share, Error> {
|
||||
await repository.shareSources([], transferName: transferName, senderName: senderName, accessPolicy: accessPolicy)
|
||||
private(set) var shareDestinations: [ShareDestination] = []
|
||||
|
||||
func sharePickedFiles(repository: CoreGateway, files: [PickedShareFile], transferName: String, senderName: String, destination: ShareDestination) async -> Result<Share, Error> {
|
||||
shareDestinations.append(destination)
|
||||
switch destination {
|
||||
case .invitation(let accessPolicy):
|
||||
return await repository.shareSources(
|
||||
[], transferName: transferName, senderName: senderName, accessPolicy: accessPolicy
|
||||
)
|
||||
case .contact(let endpointId):
|
||||
return await repository.sendToContact(
|
||||
endpointId: endpointId, sources: [], transferName: transferName, senderName: senderName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user