Skip to content

Commit

Permalink
SwiftPM project
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrejKolar committed Jun 8, 2022
1 parent 7451e76 commit 9426345
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
22 changes: 22 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "PassGen",
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.executableTarget(
name: "PassGen",
dependencies: []),
.testTarget(
name: "PassGenTests",
dependencies: ["PassGen"]),
]
)
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# PassGen

A description of this package.
92 changes: 92 additions & 0 deletions Sources/PassGen/PasswordGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
// PasswordGenerator.swift
//
//
// Created by Andrej Kolar on 6/8/22.
//

import Foundation

struct PasswordGenerator {
public struct Defaults {
static let length = 16
static let wordLength = 8
static let wordCount = 4
static let type: PasswordType = .normal(Defaults.length)
static let characters: Set<CharacterType> = [
.uppercase,
.lowercase,
.numbers,
.symbols
]
static let separator: SeparatorType = .space
}

enum PasswordType {
case normal(Int)
case separated(Int, Int, SeparatorType)
}

enum CharacterType: String {
case uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
case lowercase = "abcdefghijklmnopqrstuvwxyz"
case numbers = "0123456789"
case symbols = "!@#$%^&*()+_-=}{[]|:;\"/?.><,`~"
}

enum SeparatorType: String {
case hyphen = "-"
case underscore = "_"
case space = " "
case slash = "/"
case verticalBar = "|"
}

public func generate(
type: PasswordType? = nil,
characters: Set<CharacterType>? = nil
) -> String {

var passwordLength = Defaults.length

var separatorIndices: [Int] = []
var separatorCharacter = Defaults.separator.rawValue

let typeOption = type ?? Defaults.type
switch typeOption {
case .normal(let length):
passwordLength = length

case .separated(let wordLength, let wordCount, let separator):
let separatorCount = wordCount - 1
passwordLength = (wordLength * wordCount) + (separatorCount)

separatorIndices = (0..<separatorCount)
.compactMap { index in
((index + 1) * wordLength) + (index * 1)
}

separatorCharacter = separator.rawValue
}

let characterOptions = characters ?? Defaults.characters
let charactersArray = characterOptions.map { $0.rawValue }

let characterArrays: [String] = (0..<passwordLength)
.compactMap { index in
guard case .separated = typeOption else {
return charactersArray.randomElement()
}

let isSeparator = separatorIndices.contains(index)

return isSeparator ? separatorCharacter : charactersArray.randomElement()
}

let pass = characterArrays.compactMap {
$0.randomElement()
}

return String(pass)
}
}
8 changes: 8 additions & 0 deletions Sources/PassGen/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
let generator = PasswordGenerator()
let password = generator.generate(
type: .separated(8, 4, .space),
characters: [.numbers, .lowercase, .uppercase]
)

print("Generated password:")
print(password)
47 changes: 47 additions & 0 deletions Tests/PassGenTests/PassGenTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import XCTest
import class Foundation.Bundle

final class PassGenTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.

// Some of the APIs that we use below are available in macOS 10.13 and above.
guard #available(macOS 10.13, *) else {
return
}

// Mac Catalyst won't have `Process`, but it is supported for executables.
#if !targetEnvironment(macCatalyst)

let fooBinary = productsDirectory.appendingPathComponent("PassGen")

let process = Process()
process.executableURL = fooBinary

let pipe = Pipe()
process.standardOutput = pipe

try process.run()
process.waitUntilExit()

let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)

XCTAssertEqual(output, "Hello, world!\n")
#endif
}

/// Returns path to the built products directory.
var productsDirectory: URL {
#if os(macOS)
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
return bundle.bundleURL.deletingLastPathComponent()
}
fatalError("couldn't find the products directory")
#else
return Bundle.main.bundleURL
#endif
}
}
143 changes: 143 additions & 0 deletions Tests/PassGenTests/PasswordGeneratorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//
// PasswordGeneratorTests.swift
//
//
// Created by Andrej Kolar on 6/8/22.
//

import XCTest

@testable import PassGen

class UnitTests_macOS: XCTestCase {

var sut: PasswordGenerator!

override func setUpWithError() throws {
try super.setUpWithError()
sut = PasswordGenerator()
}

override func tearDownWithError() throws {
sut = nil
try super.tearDownWithError()
}


func testGeneratePassword() {
XCTAssertFalse(sut.generate().isEmpty, "Password is empty")
}

func testDefaultPasswordLength() {
XCTAssertEqual(
sut.generate().count,
PasswordGenerator.Defaults.length,
"Password is not the default length"
)
}

func testCustomPasswordLength() {
let length = 17
XCTAssertEqual(
sut.generate(type: .normal(length)).count,
length,
"Password is not the passed in length"
)
}

func testDefaultOptions() {
let password = sut.generate()
let isNumeric = checkCharacters(password, type: .numbers)
let isLowercase = checkCharacters(password, type: .lowercase)
let isUppercase = checkCharacters(password, type: .uppercase)
let isSymbol = checkCharacters(password, type: .symbols)

XCTAssertFalse(isNumeric, "Password should not contain only numbers")
XCTAssertFalse(isLowercase, "Password should not contain only lowercase letters")
XCTAssertFalse(isUppercase, "Password should not contain only uppercase letters")
XCTAssertFalse(isSymbol, "Password should not contain only symbols")
}

func testOnlyNumbers() {
let password = sut.generate(characters: [.numbers])
let isNumeric = checkCharacters(password, type: .numbers)
XCTAssertTrue(isNumeric, "Password is not number only")
}

func testOnlyLowerCase() {
let password = sut.generate(characters: [.lowercase])
let isLowercase = checkCharacters(password, type: .lowercase)
XCTAssertTrue(isLowercase, "Password should contain only lowercase letters")
}

func testOnlyUppercase() {
let password = sut.generate(characters: [.uppercase])
let isUppercase = checkCharacters(password, type: .uppercase)
XCTAssertTrue(isUppercase, "Password should contain only uppercase letters")
}

func testOnlySymbols() {
let password = sut.generate(characters: [.symbols])
let isSymbol = checkCharacters(password, type: .symbols)

XCTAssertTrue(isSymbol, "Password should contain only symbols")
}

func testWithSeparatorsLength() {
let wordLength = 4
let wordCount = 4

let length = lengthWithSeparator(wordLength: wordLength, wordCount: wordCount)

let password = sut.generate(
type: .separated(wordLength, wordCount, .verticalBar),
characters: [.uppercase]
)

XCTAssertEqual(
password.count,
length,
"Password is not the correct length"
)
}

func testWithSeparatorsCharacters() {
let wordLength = 4
let wordCount = 4

let password = sut.generate(
type: .separated(wordLength, wordCount, .verticalBar),
characters: [.uppercase]
)

XCTAssertTrue(checkIfSeparator(password, index: 4, separator: "|"), "Character is not a separator")
XCTAssertTrue(checkIfSeparator(password, index: 9, separator: "|"), "Character is not a separator")
XCTAssertTrue(checkIfSeparator(password, index: 14, separator: "|"), "Character is not a separator")

XCTAssertFalse(checkIfSeparator(password, index: 1, separator: "|"), "Separator at wrong place")
XCTAssertFalse(checkIfSeparator(password, index: 10, separator: "|"), "Separator at wrong place")
XCTAssertFalse(checkIfSeparator(password, index: 13, separator: "|"), "Separator at wrong place")

XCTAssertFalse(checkIfSeparator(password, index: 4, separator: "_"), "Wrong separator character")
XCTAssertFalse(checkIfSeparator(password, index: 9, separator: " "), "Wrong separator character")
XCTAssertFalse(checkIfSeparator(password, index: 14, separator: "/"), "Wrong separator character")
}

// Helpers

private func checkCharacters(_ password: String, type: PasswordGenerator.CharacterType) -> Bool {
let typeCharacterSet = CharacterSet(charactersIn: type.rawValue)
let passwordCharacterSet = CharacterSet(charactersIn: password)
return passwordCharacterSet.isSubset(of: typeCharacterSet)

}

private func lengthWithSeparator(wordLength: Int, wordCount: Int) -> Int {
(wordLength * wordCount) + (wordCount - 1)
}

private func checkIfSeparator(_ password: String, index: Int, separator: Character) -> Bool {
let char = password[password.index(password.startIndex, offsetBy: index)]
return char == separator
}
}

0 comments on commit 9426345

Please sign in to comment.