krz/keycask

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

Tests/KeycaskCLITests/AddShowTests.swift

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

124 lines · 5156 bytes

  1import Foundation
  2import Testing
  3
  4@Suite struct AddShowTests {
  5    @Test func addReadsPasswordFromStdinAndPrintsID() throws {
  6        let cli = try CLI.initialized()
  7        let r = try cli.run(
  8            ["add", "github", "-u", "cmc", "--url", "https://github.com", "--tag", "dev"],
  9            stdin: "hunter2\n")
 10        #expect(r.status == 0)
 11        let id = r.stdout.trimmingCharacters(in: .whitespacesAndNewlines)
 12        #expect(id.count == 8)
 13
 14        let shown = try cli.run(["show", id])
 15        #expect(shown.status == 0)
 16        #expect(shown.stdout.contains("name: github"))
 17        #expect(shown.stdout.contains("username: cmc"))
 18        #expect(shown.stdout.contains("password: ********"))
 19        #expect(shown.stdout.contains("tags: dev"))
 20        #expect(!shown.stdout.contains("hunter2"))
 21    }
 22
 23    @Test func showByNameRevealAndField() throws {
 24        let cli = try CLI.initialized()
 25        try cli.run(["add", "github"], stdin: "hunter2\n")
 26        let revealed = try cli.run(["show", "github", "--reveal"])
 27        #expect(revealed.stdout.contains("password: hunter2"))
 28        let field = try cli.run(["show", "github", "--field", "password"])
 29        #expect(field.stdout == "hunter2\n")
 30        let missing = try cli.run(["show", "github", "--field", "url"])
 31        #expect(missing.status == 0)
 32        #expect(missing.stdout == "\n")
 33        let unknown = try cli.run(["show", "github", "--field", "nope"])
 34        #expect(unknown.status == 2)
 35    }
 36
 37    @Test func jsonMasksUnlessReveal() throws {
 38        let cli = try CLI.initialized()
 39        try cli.run(["add", "github", "-u", "cmc"], stdin: "hunter2\n")
 40        let masked = try cli.run(["show", "github", "--json"])
 41        let obj = try JSONSerialization.jsonObject(with: Data(masked.stdout.utf8)) as! [String: Any]
 42        #expect(obj["name"] as? String == "github")
 43        #expect(obj["username"] as? String == "cmc")
 44        #expect(obj["password"] as? String == "********")
 45        #expect((obj["id"] as? String)?.count == 8)
 46        #expect((obj["created"] as? String)?.hasSuffix("Z") == true)
 47        let revealed = try cli.run(["show", "github", "--json", "--reveal"])
 48        let obj2 =
 49            try JSONSerialization.jsonObject(with: Data(revealed.stdout.utf8)) as! [String: Any]
 50        #expect(obj2["password"] as? String == "hunter2")
 51    }
 52
 53    @Test func addGenerateAndWords() throws {
 54        let cli = try CLI.initialized()
 55        try cli.run(["add", "a", "--generate"])
 56        try cli.run(["add", "b", "--generate", "--length", "40"])
 57        try cli.run(["add", "c", "--words", "4"])
 58        #expect(try cli.run(["show", "a", "--field", "password"]).stdout.count == 25)
 59        #expect(try cli.run(["show", "b", "--field", "password"]).stdout.count == 41)
 60        let words = try cli.run(["show", "c", "--field", "password"]).stdout
 61            .trimmingCharacters(in: .newlines).split(separator: "-")
 62        #expect(words.count == 4)
 63    }
 64
 65    @Test func generateAndWordsTogetherIsUsageError() throws {
 66        let cli = try CLI.initialized()
 67        let r = try cli.run(["add", "a", "--generate", "--words", "3"])
 68        #expect(r.status == 2)
 69    }
 70
 71    @Test func duplicateNamesAreAllowedAndAmbiguousOnShow() throws {
 72        let cli = try CLI.initialized()
 73        let a = try cli.run(["add", "gh", "-u", "one", "--generate"]).stdout.trimmingCharacters(
 74            in: .newlines)
 75        let b = try cli.run(["add", "gh", "-u", "two", "--generate"]).stdout.trimmingCharacters(
 76            in: .newlines)
 77        let r = try cli.run(["show", "gh"])
 78        #expect(r.status == 5)
 79        #expect(r.stderr.contains(a) && r.stderr.contains(b))
 80        #expect(try cli.run(["show", a]).stdout.contains("username: one"))
 81    }
 82
 83    @Test func missingEntryIsNotFound() throws {
 84        let cli = try CLI.initialized()
 85        let r = try cli.run(["show", "nope"])
 86        #expect(r.status == 3)
 87        #expect(r.stderr == "nope: not found\n")
 88    }
 89
 90    @Test func wrongPassphraseCannotDecrypt() throws {
 91        let cli = try CLI.initialized()
 92        let r = try cli.run(["show", "x"], passphrase: "wrong")
 93        #expect(r.status == 4)
 94        #expect(r.stderr.contains("cannot decrypt"))
 95    }
 96
 97    @Test func missingVaultIsNotFound() throws {
 98        let cli = try CLI()
 99        let r = try cli.run(["show", "x"])
100        #expect(r.status == 3)
101        #expect(r.stderr.contains("keycask init"))
102    }
103
104    @Test func lengthWithWordsIsUsageError() throws {
105        let cli = try CLI.initialized()
106        let r = try cli.run(["add", "a", "--words", "4", "--length", "30"])
107        #expect(r.status == 2)
108    }
109
110    @Test func addWithoutPasswordSourceIsUsageError() throws {
111        let cli = try CLI.initialized()
112        let r = try cli.run(["add", "x"], stdin: "")
113        #expect(r.status == 2)
114        #expect(r.stderr.contains("no password"))
115    }
116
117    @Test func corruptVaultIsFailure() throws {
118        let cli = try CLI.initialized()
119        try Data("{}".utf8).write(to: cli.vault)
120        let r = try cli.run(["show", "x"])
121        #expect(r.status == 1)
122        #expect(r.stderr.hasPrefix("vault is corrupt"))
123    }
124}