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