RadrootsDocumentPresentationTests.swift (7319B)
1 import Foundation 2 import Testing 3 import UniformTypeIdentifiers 4 @testable import RadrootsKit 5 6 @Test func documentPresentationMapsImportContentTypes() throws { 7 let request = try RadrootsDocumentImportRequest( 8 allowedContentKinds: [.json, .plainText, .url, .file, .stagedBlob] 9 ) 10 11 let types = RadrootsDocumentPresentationAdapter.contentTypes(for: request) 12 13 #expect(types == [.json, .plainText, .url, .item, .data]) 14 #expect(RadrootsDocumentPresentationAdapter.contentType(forMediaType: "application/json") == .json) 15 #expect(RadrootsDocumentPresentationAdapter.contentType(forMediaType: "text/plain") == .plainText) 16 #expect(RadrootsDocumentPresentationAdapter.contentType(forMediaType: nil) == .data) 17 } 18 19 @Test func documentPresentationBuildsImportDestinations() throws { 20 let destination = try RadrootsDocumentPresentationAdapter.importDestination( 21 sourceURL: URL(fileURLWithPath: "/tmp/relays.json"), 22 scope: .data, 23 importID: "import_1" 24 ) 25 26 #expect(destination.scope == .data) 27 #expect(destination.relativePath == "document_import/import_1/relays.json") 28 29 #expect(throws: RadrootsDocumentInterchangeError.self) { 30 _ = try RadrootsDocumentPresentationAdapter.importDestination( 31 sourceURL: URL(fileURLWithPath: "/tmp/../bad.json"), 32 scope: .data, 33 importID: "../escape" 34 ) 35 } 36 } 37 38 @Test func documentPresentationAdaptsPublicShareItems() throws { 39 let textRequest = try RadrootsShareRequest(items: [.text(" public post ")], subject: " Radroots ") 40 let textItem = try RadrootsDocumentPresentationAdapter.transferItem(for: textRequest) 41 42 #expect(textItem.payload == .text("public post")) 43 #expect(textItem.text == "public post") 44 #expect(textItem.subject == "Radroots") 45 46 let urlRequest = try RadrootsShareRequest(items: [.url(URL(string: "https://radroots.org/posts/1")!)]) 47 let urlItem = try RadrootsDocumentPresentationAdapter.transferItem(for: urlRequest) 48 49 #expect(urlItem.payload == .url(URL(string: "https://radroots.org/posts/1")!)) 50 #expect(urlItem.text == "https://radroots.org/posts/1") 51 52 let file = RadrootsFileReference(scope: .data, relativePath: "exports/diagnostics.json") 53 let fileRequest = try RadrootsShareRequest( 54 items: [.file(file, suggestedFilename: "diagnostics.json", mediaType: "application/json", sizeBytes: nil)] 55 ) 56 57 #expect(throws: RadrootsDocumentInterchangeError.self) { 58 _ = try RadrootsDocumentPresentationAdapter.transferItem(for: fileRequest) 59 } 60 } 61 62 @Test func documentPresentationPreparesScopedFileShareItems() throws { 63 let access = try testDocumentPresentationFileAccess() 64 let file = RadrootsFileReference(scope: .data, relativePath: "exports/diagnostics.json") 65 let data = Data(#"{"status":"ok"}"#.utf8) 66 try access.write(.inline(data), to: file) 67 let request = try RadrootsShareRequest( 68 items: [ 69 .file( 70 file, 71 suggestedFilename: " diagnostics.json ", 72 mediaType: " Application/JSON ", 73 sizeBytes: UInt64(data.count) 74 ) 75 ], 76 subject: " Radroots " 77 ) 78 79 let item = try RadrootsDocumentPresentationAdapter.transferItem(for: request, fileAccess: access) 80 guard let prepared = item.preparedExport else { 81 Issue.record("expected prepared file share item") 82 return 83 } 84 85 #expect(item.subject == "Radroots") 86 #expect(prepared.suggestedFilename == "diagnostics.json") 87 #expect(prepared.mediaType == "application/json") 88 #expect(prepared.sizeBytes == UInt64(data.count)) 89 #expect(try Data(contentsOf: prepared.fileURL) == data) 90 #expect(try access.preparedExportExists(prepared)) 91 } 92 93 @Test func documentPresentationPreparesStagedBlobShareItems() throws { 94 let access = try testDocumentPresentationFileAccess() 95 let data = Data("staged export".utf8) 96 let staged = try access.stageBlob( 97 data, 98 mediaType: "text/plain", 99 filenameHint: "staged-note.txt" 100 ) 101 let request = try RadrootsShareRequest( 102 items: [.stagedBlob(staged, suggestedFilename: nil)], 103 subject: nil 104 ) 105 106 let item = try RadrootsDocumentPresentationAdapter.transferItem(for: request, fileAccess: access) 107 guard let prepared = item.preparedExport else { 108 Issue.record("expected prepared staged blob share item") 109 return 110 } 111 112 #expect(prepared.suggestedFilename == "staged-note.txt") 113 #expect(prepared.mediaType == "text/plain") 114 #expect(prepared.sizeBytes == UInt64(data.count)) 115 #expect(try Data(contentsOf: prepared.fileURL) == data) 116 } 117 118 @Test func documentPresentationRejectsUnsafeShareFilesAndSecretMaterial() throws { 119 #expect(throws: RadrootsDocumentInterchangeError.self) { 120 _ = try RadrootsShareRequest( 121 items: [ 122 .file( 123 RadrootsFileReference(scope: .data, relativePath: "/tmp/private.txt"), 124 suggestedFilename: "private.txt", 125 mediaType: "text/plain", 126 sizeBytes: nil 127 ) 128 ] 129 ) 130 } 131 #expect(throws: RadrootsDocumentInterchangeError.self) { 132 _ = try RadrootsShareRequest(items: [.text("nostr:nsec1qqqqqq")]) 133 } 134 #expect(throws: RadrootsDocumentInterchangeError.self) { 135 _ = try RadrootsShareRequest( 136 items: [ 137 .file( 138 RadrootsFileReference(scope: .data, relativePath: "identity/public.json"), 139 suggestedFilename: "selected_secret_hex.json", 140 mediaType: "application/json", 141 sizeBytes: nil 142 ) 143 ] 144 ) 145 } 146 } 147 148 @Test func preparedExportFileDocumentWrapsPreparedExportURL() throws { 149 let directory = FileManager.default.temporaryDirectory 150 .appendingPathComponent("radroots-prepared-export-\(UUID().uuidString)", isDirectory: true) 151 try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) 152 let fileURL = directory.appendingPathComponent("diagnostics.json") 153 let data = Data(#"{"status":"ok"}"#.utf8) 154 try data.write(to: fileURL) 155 let prepared = try RadrootsPreparedExportDocument( 156 preparedID: "prepared_1", 157 fileURL: fileURL, 158 suggestedFilename: "diagnostics.json", 159 mediaType: "application/json", 160 sizeBytes: UInt64(data.count) 161 ) 162 163 let document = RadrootsPreparedExportFileDocument(preparedExport: prepared) 164 165 #expect(document.fileURL == fileURL.standardizedFileURL) 166 #expect(RadrootsPreparedExportFileDocument.readableContentTypes == [.data]) 167 } 168 169 private func testDocumentPresentationFileAccess() throws -> RadrootsAppleFileAccess { 170 let root = FileManager.default.temporaryDirectory 171 .appendingPathComponent("radroots-document-presentation-\(UUID().uuidString)", isDirectory: true) 172 let roots = try RadrootsAppleFileRoots( 173 appIdentifier: "org.radroots.document-presentation.tests", 174 dataRoot: root.appendingPathComponent("data", isDirectory: true), 175 cacheRoot: root.appendingPathComponent("cache", isDirectory: true), 176 temporaryRoot: root.appendingPathComponent("tmp", isDirectory: true) 177 ) 178 return RadrootsAppleFileAccess(roots: roots) 179 }