diff --git a/CMakeLists.txt b/CMakeLists.txt index 22ab15a..8c0fbaf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,18 @@ -cmake_minimum_required(VERSION 3.17) -project(passwdgen) +cmake_minimum_required(VERSION 3.10) +project(passwdgen VERSION 1.0) set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) -add_executable(passwdgen passwdgen.cpp) +# Create a library with the core functionality +add_library(passwdgen_lib passwdgen.cpp) + +# Main application +add_executable(passwdgen main.cpp) +target_link_libraries(passwdgen passwdgen_lib) + +# Enable testing +enable_testing() + +# Add test directory +add_subdirectory(tests) diff --git a/README.md b/README.md index 7e7dffe..00a8f42 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,19 @@ make ``` Then, just copy the compiled binary to somewhere on your $PATH + +## Testing + +To run the tests: +```bash +mkdir build +cd build +cmake .. +make +ctest +``` + +or directly: +```bash +./tests/passwdgen_test +``` diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..230a7bc --- /dev/null +++ b/main.cpp @@ -0,0 +1,46 @@ +// +// Created by gmgauthier on 05/09/25. +// +// main.cpp +#include "passwdgen.h" +#include +#include + +int main(int argc, char *argv[]) { + int passwordLength = 0; + bool punc = false; + + if (argc < 1) { + std::cout << random_string(32, false) << std::endl; + return 0; + } + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if ((arg == "-h") || (arg == "--help")) { + show_usage(argv[0]); + return 0; + } + if (arg == "-p") { + punc = true; + } else if ((arg == "-l") || (arg == "--length")) { + if (not argv[i +1]) { + std::cerr << "Specify a password length" << std::endl; + return 1; + } + std::string passwordLengthStr = argv[i + 1]; + try { + passwordLength = std::stoi(passwordLengthStr); + } catch ([[maybe_unused]] const std::invalid_argument& e){ + std::cerr << "Length must be a valid integer" << std::endl; + return 1; + } + } + } + + if (passwordLength == 0) { + passwordLength = 32; + } + std::string password = random_string(passwordLength, punc); + std::cout << password << std::endl; + return 0; +} diff --git a/passwdgen.cpp b/passwdgen.cpp index 274a561..2fb4e8f 100644 --- a/passwdgen.cpp +++ b/passwdgen.cpp @@ -1,12 +1,8 @@ -// -// passwdgen.cpp -// Created by Greg Gauthier on 2020.10.22. -// - +// passwdgen.cpp +#include "passwdgen.h" #include #include - std::string random_string(std::size_t length, bool punc) { std::string CHARACTERS; std::string ALPHANUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; @@ -37,43 +33,3 @@ void show_usage(const std::string& name) { << "\t-p, --punctuation \t\tToggle special characters (default: false)" << std::endl; } - -int main(int argc, char *argv[]) { - int passwordLength = 0; - bool punc = false; - - if (argc < 1) { - std::cout << random_string(32, false) << std::endl; - return 0; - } - for (int i = 1; i < argc; ++i) { - std::string arg = argv[i]; - if ((arg == "-h") || (arg == "--help")) { - show_usage(argv[0]); - return 0; - } - if (arg == "-p") { - punc = true; - } else if ((arg == "-l") || (arg == "--length")) { - if (not argv[i +1]) { - std::cerr << "Specify a password length" << std::endl; - return 1; - } - std::string passwordLengthStr = argv[i + 1]; - try { - passwordLength = std::stoi(passwordLengthStr); - } catch ([[maybe_unused]] const std::invalid_argument& e){ - std::cerr << "Length must be a valid integer" << std::endl; - return 1; - } - } - } - - if (passwordLength == 0) { - passwordLength = 32; - } - std::string password = random_string(passwordLength, punc); - std::cout << password << std::endl; - return 0; -} - diff --git a/passwdgen.h b/passwdgen.h new file mode 100644 index 0000000..8c07b6d --- /dev/null +++ b/passwdgen.h @@ -0,0 +1,10 @@ +// passwdgen.h +#pragma once + +#include + +// Core password generation functionality +std::string random_string(std::size_t length, bool punc); + +// Help message display +void show_usage(const std::string& name); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..3743a1e --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,17 @@ +# tests/CMakeLists.txt +add_executable(passwdgen_test passwdgen_test.cpp) + +# Link against the library that contains the functions we want to test +target_link_libraries(passwdgen_test passwdgen_lib) + +# Register the test with CTest +add_test( + NAME passwdgen_tests + COMMAND passwdgen_test +) + +# Define what it means for the test to pass +set_tests_properties(passwdgen_tests + PROPERTIES PASS_REGULAR_EXPRESSION "All tests passed!" +) + diff --git a/tests/passwdgen_test.cpp b/tests/passwdgen_test.cpp new file mode 100644 index 0000000..7f5f645 --- /dev/null +++ b/tests/passwdgen_test.cpp @@ -0,0 +1,50 @@ +// tests/passwdgen_test.cpp +#include "../passwdgen.h" +#include +#include +#include + +void test_random_string_length() { + const int lengths[] = {0, 1, 10, 32, 100}; + + for (auto length : lengths) { + std::string result = random_string(length, false); + assert(result.length() == length); + + std::string result_with_punc = random_string(length, true); + assert(result_with_punc.length() == length); + } + std::cout << "Length test passed\n"; +} + +void test_alphanumeric_only() { + std::regex alphanumeric_only("^[a-zA-Z0-9]*$"); + + for (int i = 0; i < 10; i++) { + std::string result = random_string(20, false); + assert(std::regex_match(result, alphanumeric_only)); + } + std::cout << "Alphanumeric test passed\n"; +} + +void test_with_punctuation() { + bool found_special = false; + + for (int i = 0; i < 100 && !found_special; i++) { + std::string result = random_string(100, true); + if (!std::regex_match(result, std::regex("^[a-zA-Z0-9]*$"))) { + found_special = true; + } + } + + assert(found_special); + std::cout << "Punctuation test passed\n"; +} + +int main() { + test_random_string_length(); + test_alphanumeric_only(); + test_with_punctuation(); + std::cout << "All tests passed!\n"; + return 0; +}