krz/keycask

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

Tests/KeycaskCLITests/InitTests.swift

105 lines · 4169 bytes

  1import Foundation
  2import Testing
  3
  4@Suite struct InitTests {
  5    @Test func initCreatesVaultAndPrintsPath() throws {
  6        let cli = try CLI()
  7        let r = try cli.run(["init"])
  8        #expect(r.status == 0)
  9        #expect(r.stdout.contains(cli.vault.path))
 10        #expect(FileManager.default.fileExists(atPath: cli.vault.path))
 11        let text = try String(contentsOf: cli.vault, encoding: .utf8)
 12        #expect(text.contains("\"format\" : 1"))
 13        #expect(text.contains("pbkdf2-hmac-sha256"))
 14        #expect(!text.contains("entries"))
 15    }
 16
 17    @Test func initRefusesExistingVault() throws {
 18        let cli = try CLI.initialized()
 19        let r = try cli.run(["init"])
 20        #expect(r.status == 1)
 21        #expect(r.stderr.contains("already exists"))
 22    }
 23
 24    @Test func initWithoutPassphraseOrTTYIsUsageError() throws {
 25        let cli = try CLI()
 26        let r = try cli.run(["init"], passphrase: nil)
 27        #expect(r.status == 2)
 28        #expect(r.stderr.contains("KEYCASK_PASSPHRASE"))
 29    }
 30
 31    @Test func emptyPassphraseIsRejected() throws {
 32        let cli = try CLI()
 33        let r = try cli.run(["init"], passphrase: "")
 34        #expect(r.status == 1)
 35        #expect(r.stderr.contains("empty"))
 36    }
 37
 38    @Test func vaultFlagBeatsEnvironment() throws {
 39        let cli = try CLI()
 40        let other = cli.dir.appendingPathComponent("elsewhere.kc")
 41        let r = try cli.run(["--vault", other.path, "init"])
 42        #expect(r.status == 0)
 43        #expect(FileManager.default.fileExists(atPath: other.path))
 44        #expect(!FileManager.default.fileExists(atPath: cli.vault.path))
 45    }
 46
 47    @Test func unknownSubcommandIsUsageError() throws {
 48        let cli = try CLI()
 49        let r = try cli.run(["frobnicate"])
 50        #expect(r.status == 2)
 51        #expect(r.stderr.contains("Usage"))
 52    }
 53
 54    @Test func helpExitsZero() throws {
 55        let cli = try CLI()
 56        let r = try cli.run(["--help"])
 57        #expect(r.status == 0)
 58        #expect(r.stdout.contains("init"))
 59    }
 60
 61    #if !os(Windows)
 62        @Test func vaultIsPrivateOnUnix() throws {
 63            let cli = try CLI.initialized()
 64            let attrs = try FileManager.default.attributesOfItem(atPath: cli.vault.path)
 65            let mode = (attrs[.posixPermissions] as! NSNumber).intValue & 0o777
 66            #expect(mode == 0o600)
 67        }
 68
 69        @Test func preExistingTempFileDoesNotWeakenPermissions() throws {
 70            let cli = try CLI()
 71            let temp = cli.dir.appendingPathComponent("vault.kc.tmp")
 72            FileManager.default.createFile(
 73                atPath: temp.path, contents: Data(), attributes: [.posixPermissions: 0o644])
 74
 75            let r = try cli.run(["init"])
 76            #expect(r.status == 0)
 77            let attrs = try FileManager.default.attributesOfItem(atPath: cli.vault.path)
 78            let mode = (attrs[.posixPermissions] as! NSNumber).intValue & 0o777
 79            #expect(mode == 0o600)
 80            #expect(!FileManager.default.fileExists(atPath: temp.path))
 81        }
 82
 83        @Test func symlinkedTempFileIsNotFollowed() throws {
 84            let cli = try CLI()
 85            let victim = cli.dir.appendingPathComponent("victim.txt")
 86            FileManager.default.createFile(atPath: victim.path, contents: Data())
 87            let temp = cli.dir.appendingPathComponent("vault.kc.tmp")
 88            try FileManager.default.createSymbolicLink(at: temp, withDestinationURL: victim)
 89
 90            let r = try cli.run(["init"])
 91            #expect(r.status == 0)
 92            let victimAttrs = try FileManager.default.attributesOfItem(atPath: victim.path)
 93            #expect((victimAttrs[.size] as! NSNumber).intValue == 0)
 94            let attrs = try FileManager.default.attributesOfItem(atPath: cli.vault.path)
 95            #expect(attrs[.type] as? FileAttributeType == .typeRegular)
 96            #expect((attrs[.posixPermissions] as! NSNumber).intValue & 0o777 == 0o600)
 97        }
 98    #endif
 99
100    @Test func noTempFileLeftBehind() throws {
101        let cli = try CLI.initialized()
102        let names = try FileManager.default.contentsOfDirectory(atPath: cli.dir.path)
103        #expect(names == ["vault.kc"])
104    }
105}