krz/keycask

Password manager: Swift core library, CLI for macOS/Linux/Windows, iOS/macOS app. cli password-manager swift

Tests/KeycaskCLITests/ClipboardTests.swift

main
keycask/Tests/KeycaskCLITests/ClipboardTests.swift history · blame · raw

168 lines · 6507 bytes

  1import Foundation
  2import Testing
  3
  4@testable import keycask
  5
  6@Suite struct ClipboardTests {
  7    @Test func clearsOnlyWhenClipboardStillHoldsTheSecret() {
  8        #expect(Clipboard.shouldClear(secret: "s", current: "s"))
  9        #expect(!Clipboard.shouldClear(secret: "s", current: "user pasted"))
 10        #expect(!Clipboard.shouldClear(secret: "s", current: nil))
 11    }
 12
 13    @Test func handoffRoundTrips() throws {
 14        let h = Clipboard.Handoff(secret: "s3cret")
 15        let data = try JSONEncoder().encode(h)
 16        #expect(try JSONDecoder().decode(Clipboard.Handoff.self, from: data) == h)
 17    }
 18
 19    @Test func stripsOneTrailingNewline() {
 20        #expect(Clipboard.stripTrailingNewline("s\r\n") == "s")
 21        #expect(Clipboard.stripTrailingNewline("s\n") == "s")
 22        #expect(Clipboard.stripTrailingNewline("s") == "s")
 23        #expect(Clipboard.stripTrailingNewline("s\n\n") == "s\n")
 24    }
 25
 26    @Test func findToolScansPathInOrder() throws {
 27        let dir = FileManager.default.temporaryDirectory
 28            .appendingPathComponent("keycask-clip-\(UUID().uuidString)")
 29        try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
 30        #expect(Clipboard.findTool(path: dir.path) == nil)
 31
 32        #if os(macOS)
 33            let names = ["pbcopy", "pbpaste"]
 34        #elseif os(Windows)
 35            let names = ["clip.exe", "powershell.exe"]
 36        #else
 37            let names = ["xclip"]
 38        #endif
 39        for n in names {
 40            let f = dir.appendingPathComponent(n)
 41            try Data("#!/bin/sh\n".utf8).write(to: f)
 42            try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: f.path)
 43        }
 44        let tool = Clipboard.findTool(path: dir.path)
 45        #expect(tool != nil)
 46        #expect(tool?.copy.first?.hasPrefix(dir.path) == true)
 47    }
 48
 49    @Test func daemonWithoutHandoffFails() throws {
 50        let cli = try CLI()
 51        let r = try cli.run(["clipboard-daemon", "1"], stdin: "not json", passphrase: nil)
 52        #expect(r.status == 1)
 53    }
 54
 55    @Test func daemonIsHiddenFromHelp() throws {
 56        let cli = try CLI()
 57        let r = try cli.run(["--help"], passphrase: nil)
 58        #expect(!r.stdout.contains("clipboard-daemon"))
 59        #expect(r.stdout.contains("clip"))
 60    }
 61
 62    @Test func clipOfMissingEntryIsNotFoundBeforeTouchingClipboard() throws {
 63        let cli = try CLI.initialized()
 64        let r = try cli.run(["clip", "nope"])
 65        #expect(r.status == 3)
 66    }
 67
 68    #if !os(Windows)
 69        /// A temp directory of clipboard tools backed by a file, so tests never
 70        /// touch the real clipboard.
 71        struct Stub {
 72            let dir: URL
 73
 74            var file: URL { dir.appendingPathComponent("clip.txt") }
 75            var environment: [String: String] { ["PATH": "\(dir.path):/bin:/usr/bin"] }
 76
 77            init() throws {
 78                dir = FileManager.default.temporaryDirectory
 79                    .appendingPathComponent("keycask-clip-stub-\(UUID().uuidString)")
 80                try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
 81                #if os(macOS)
 82                    try install("pbcopy", "#!/bin/sh\ncat > \"$(dirname \"$0\")/clip.txt\"\n")
 83                    try install(
 84                        "pbpaste", "#!/bin/sh\ncat \"$(dirname \"$0\")/clip.txt\" 2>/dev/null\n")
 85                #else
 86                    try install(
 87                        "xclip",
 88                        """
 89                        #!/bin/sh
 90                        for a in "$@"; do
 91                            if [ "$a" = "-o" ]; then
 92                                cat "$(dirname "$0")/clip.txt" 2>/dev/null
 93                                exit 0
 94                            fi
 95                        done
 96                        cat > "$(dirname "$0")/clip.txt"
 97
 98                        """)
 99                #endif
100            }
101
102            func contents() -> String {
103                (try? String(contentsOf: file, encoding: .utf8)) ?? ""
104            }
105
106            func set(_ text: String) throws {
107                try Data(text.utf8).write(to: file)
108            }
109
110            private func install(_ name: String, _ script: String) throws {
111                let url = dir.appendingPathComponent(name)
112                try Data(script.utf8).write(to: url)
113                try FileManager.default.setAttributes(
114                    [.posixPermissions: 0o755], ofItemAtPath: url.path)
115            }
116        }
117
118        @Test func clipCopiesTheFieldThroughTheTool() throws {
119            let cli = try CLI.initialized()
120            let stub = try Stub()
121            try cli.run(["add", "t", "--generate"])
122            let expected = try cli.run(["show", "t", "--field", "password"]).stdout
123                .trimmingCharacters(in: .whitespacesAndNewlines)
124
125            let start = Date()
126            let r = try cli.run(["clip", "t"], extraEnvironment: stub.environment)
127            let elapsed = Date().timeIntervalSince(start)
128
129            #expect(r.status == 0)
130            #expect(r.stdout.contains("copied password of t"))
131            #expect(stub.contents() == expected)
132            #expect(elapsed < 5)
133        }
134
135        @Test func generateCopyWritesTheTool() throws {
136            let cli = try CLI()
137            let stub = try Stub()
138            let r = try cli.run(
139                ["generate", "--copy"], passphrase: nil, extraEnvironment: stub.environment)
140            #expect(r.status == 0)
141            let copied = stub.contents()
142            #expect(copied.count == 24)
143            #expect(!r.stdout.contains(copied))
144        }
145
146        @Test func daemonClearsWhenClipboardStillHoldsSecret() throws {
147            let cli = try CLI()
148            let stub = try Stub()
149            try stub.set("probe")
150            let r = try cli.run(
151                ["clipboard-daemon", "1"], stdin: #"{"secret":"probe"}"#, passphrase: nil,
152                extraEnvironment: stub.environment)
153            #expect(r.status == 0)
154            #expect(stub.contents() == "")
155        }
156
157        @Test func daemonLeavesForeignContentAlone() throws {
158            let cli = try CLI()
159            let stub = try Stub()
160            try stub.set("other")
161            let r = try cli.run(
162                ["clipboard-daemon", "1"], stdin: #"{"secret":"probe"}"#, passphrase: nil,
163                extraEnvironment: stub.environment)
164            #expect(r.status == 0)
165            #expect(stub.contents() == "other")
166        }
167    #endif
168}