Cybersecurity-Projects/PROJECTS/beginner/hash-cracker/tests/test_rules.cpp

114 lines
3.6 KiB
C++

/*
©AngelaMos | 2026
test_rules.cpp
Tests for password mutation rules and rule-based attack integration
Verifies each individual mutation generator: capitalize_first, uppercase_all,
leet_speak, append_digits (1000 candidates), prepend_digits, reverse,
toggle_case. Tests apply_all produces >2000 mutations and contains expected
entries. Integration tests confirm RuleAttack applies mutations to every
dictionary word and that chain_rules mode produces more candidates than
single-pass mode.
Connects to:
rules/RuleSet.hpp - individual mutation functions tested
attack/RuleAttack.hpp - RuleAttack integration tested
tests/data/small_wordlist.txt - fixture wordlist
*/
#include "src/attack/RuleAttack.hpp"
#include "src/rules/RuleSet.hpp"
#include <algorithm>
#include <gtest/gtest.h>
#include <ranges>
#include <vector>
static std::vector<std::string> collect(std::generator<std::string> gen) {
return std::ranges::to<std::vector<std::string>>(std::move(gen));
}
TEST(RuleSetTest, CapitalizeFirst) {
auto results = collect(RuleSet::capitalize_first("password"));
ASSERT_EQ(results.size(), 1);
EXPECT_EQ(results[0], "Password");
}
TEST(RuleSetTest, UppercaseAll) {
auto results = collect(RuleSet::uppercase_all("password"));
ASSERT_EQ(results.size(), 1);
EXPECT_EQ(results[0], "PASSWORD");
}
TEST(RuleSetTest, LeetSpeak) {
auto results = collect(RuleSet::leet_speak("password"));
ASSERT_EQ(results.size(), 1);
EXPECT_EQ(results[0], "p@$$w0rd");
}
TEST(RuleSetTest, AppendDigits) {
auto results = collect(RuleSet::append_digits("pass"));
EXPECT_EQ(results.size(), 1000);
EXPECT_EQ(results[0], "pass0");
EXPECT_EQ(results[999], "pass999");
}
TEST(RuleSetTest, PrependDigits) {
auto results = collect(RuleSet::prepend_digits("pass"));
EXPECT_EQ(results.size(), 1000);
EXPECT_EQ(results[0], "0pass");
}
TEST(RuleSetTest, Reverse) {
auto results = collect(RuleSet::reverse("password"));
ASSERT_EQ(results.size(), 1);
EXPECT_EQ(results[0], "drowssap");
}
TEST(RuleSetTest, ToggleCase) {
auto results = collect(RuleSet::toggle_case("password"));
ASSERT_EQ(results.size(), 1);
EXPECT_EQ(results[0], "PASSWORD");
}
TEST(RuleSetTest, AllRulesProduceMutations) {
auto all = collect(RuleSet::apply_all("password"));
EXPECT_GT(all.size(), 2000);
EXPECT_TRUE(std::ranges::find(all, "Password") != all.end());
EXPECT_TRUE(std::ranges::find(all, "p@$$w0rd") != all.end());
EXPECT_TRUE(std::ranges::find(all, "password0") != all.end());
}
TEST(RuleAttackTest, AppliesRulesToDictionaryWords) {
auto attack = RuleAttack::create("tests/data/small_wordlist.txt", false, 0, 1);
ASSERT_TRUE(attack.has_value());
std::vector<std::string> candidates;
while (auto c = attack->next()) {
candidates.push_back(std::move(*c));
}
EXPECT_GT(candidates.size(), 10);
EXPECT_TRUE(std::ranges::find(candidates, "Password") != candidates.end());
EXPECT_TRUE(std::ranges::find(candidates, "p@$$w0rd") != candidates.end());
EXPECT_TRUE(std::ranges::find(candidates, "password") != candidates.end());
}
TEST(RuleAttackTest, ChainRulesProducesMoreCandidates) {
auto without = RuleAttack::create("tests/data/small_wordlist.txt", false, 0, 1);
auto with_chain = RuleAttack::create("tests/data/small_wordlist.txt", true, 0, 1);
ASSERT_TRUE(without.has_value());
ASSERT_TRUE(with_chain.has_value());
std::size_t count_without = 0;
while (without->next()) {
++count_without;
}
std::size_t count_with = 0;
while (with_chain->next()) {
++count_with;
}
EXPECT_GT(count_with, count_without);
}