|
| 1 | +/** @file |
| 2 | + * Unit tests for csv::internals::ColNames |
| 3 | + */ |
| 4 | + |
| 5 | +#include <catch2/catch_all.hpp> |
| 6 | +#include "internal/col_names.hpp" |
| 7 | + |
| 8 | +using namespace csv; |
| 9 | +using namespace csv::internals; |
| 10 | + |
| 11 | +// ============================================================ |
| 12 | +// Default (exact / case-sensitive) policy |
| 13 | +// ============================================================ |
| 14 | + |
| 15 | +TEST_CASE("ColNames - empty on construction", "[col_names]") { |
| 16 | + ColNames cn; |
| 17 | + REQUIRE(cn.empty()); |
| 18 | + REQUIRE(cn.size() == 0); |
| 19 | +} |
| 20 | + |
| 21 | +TEST_CASE("ColNames - set and get column names", "[col_names]") { |
| 22 | + ColNames cn; |
| 23 | + cn.set_col_names({"A", "B", "C"}); |
| 24 | + |
| 25 | + REQUIRE(cn.size() == 3); |
| 26 | + REQUIRE_FALSE(cn.empty()); |
| 27 | + |
| 28 | + auto names = cn.get_col_names(); |
| 29 | + REQUIRE(names.size() == 3); |
| 30 | + REQUIRE(names[0] == "A"); |
| 31 | + REQUIRE(names[1] == "B"); |
| 32 | + REQUIRE(names[2] == "C"); |
| 33 | +} |
| 34 | + |
| 35 | +TEST_CASE("ColNames - constructor with names", "[col_names]") { |
| 36 | + ColNames cn({"X", "Y", "Z"}); |
| 37 | + REQUIRE(cn.size() == 3); |
| 38 | + REQUIRE(cn.get_col_names() == std::vector<std::string>{"X", "Y", "Z"}); |
| 39 | +} |
| 40 | + |
| 41 | +TEST_CASE("ColNames - index_of (exact policy)", "[col_names]") { |
| 42 | + ColNames cn({"Name", "Age", "City"}); |
| 43 | + |
| 44 | + REQUIRE(cn.index_of("Name") == 0); |
| 45 | + REQUIRE(cn.index_of("Age") == 1); |
| 46 | + REQUIRE(cn.index_of("City") == 2); |
| 47 | +} |
| 48 | + |
| 49 | +TEST_CASE("ColNames - index_of returns CSV_NOT_FOUND for missing column (exact)", "[col_names]") { |
| 50 | + ColNames cn({"Name", "Age"}); |
| 51 | + |
| 52 | + REQUIRE(cn.index_of("missing") == CSV_NOT_FOUND); |
| 53 | + REQUIRE(cn.index_of("") == CSV_NOT_FOUND); |
| 54 | +} |
| 55 | + |
| 56 | +TEST_CASE("ColNames - exact policy is case-sensitive", "[col_names]") { |
| 57 | + ColNames cn({"Name", "Age"}); |
| 58 | + |
| 59 | + // Different case must not match under EXACT policy |
| 60 | + REQUIRE(cn.index_of("name") == CSV_NOT_FOUND); |
| 61 | + REQUIRE(cn.index_of("NAME") == CSV_NOT_FOUND); |
| 62 | + REQUIRE(cn.index_of("AGE") == CSV_NOT_FOUND); |
| 63 | +} |
| 64 | + |
| 65 | +TEST_CASE("ColNames - operator[] by index", "[col_names]") { |
| 66 | + ColNames cn({"First", "Second", "Third"}); |
| 67 | + |
| 68 | + REQUIRE(cn[0] == "First"); |
| 69 | + REQUIRE(cn[1] == "Second"); |
| 70 | + REQUIRE(cn[2] == "Third"); |
| 71 | +} |
| 72 | + |
| 73 | +TEST_CASE("ColNames - operator[] throws on out-of-bounds", "[col_names]") { |
| 74 | + ColNames cn({"A", "B"}); |
| 75 | + REQUIRE_THROWS_AS(cn[2], std::out_of_range); |
| 76 | + REQUIRE_THROWS_AS(cn[100], std::out_of_range); |
| 77 | +} |
| 78 | + |
| 79 | +TEST_CASE("ColNames - operator[] throws on empty ColNames", "[col_names]") { |
| 80 | + ColNames cn; |
| 81 | + REQUIRE_THROWS_AS(cn[0], std::out_of_range); |
| 82 | +} |
| 83 | + |
| 84 | +TEST_CASE("ColNames - set_col_names replaces existing names", "[col_names]") { |
| 85 | + ColNames cn({"Old1", "Old2"}); |
| 86 | + cn.set_col_names({"New1", "New2", "New3"}); |
| 87 | + |
| 88 | + REQUIRE(cn.size() == 3); |
| 89 | + REQUIRE(cn.index_of("New1") == 0); |
| 90 | + REQUIRE(cn.index_of("Old1") == CSV_NOT_FOUND); |
| 91 | +} |
| 92 | + |
| 93 | +// ============================================================ |
| 94 | +// Case-insensitive policy |
| 95 | +// ============================================================ |
| 96 | + |
| 97 | +TEST_CASE("ColNames - case-insensitive index_of: lowercase query", "[col_names][case_insensitive]") { |
| 98 | + ColNames cn; |
| 99 | + cn.set_policy(ColumnNamePolicy::CASE_INSENSITIVE); |
| 100 | + cn.set_col_names({"Name", "Age", "City"}); |
| 101 | + |
| 102 | + REQUIRE(cn.index_of("name") == 0); |
| 103 | + REQUIRE(cn.index_of("age") == 1); |
| 104 | + REQUIRE(cn.index_of("city") == 2); |
| 105 | +} |
| 106 | + |
| 107 | +TEST_CASE("ColNames - case-insensitive index_of: uppercase query", "[col_names][case_insensitive]") { |
| 108 | + ColNames cn; |
| 109 | + cn.set_policy(ColumnNamePolicy::CASE_INSENSITIVE); |
| 110 | + cn.set_col_names({"Name", "Age", "City"}); |
| 111 | + |
| 112 | + REQUIRE(cn.index_of("NAME") == 0); |
| 113 | + REQUIRE(cn.index_of("AGE") == 1); |
| 114 | + REQUIRE(cn.index_of("CITY") == 2); |
| 115 | +} |
| 116 | + |
| 117 | +TEST_CASE("ColNames - case-insensitive index_of: exact query still works", "[col_names][case_insensitive]") { |
| 118 | + ColNames cn; |
| 119 | + cn.set_policy(ColumnNamePolicy::CASE_INSENSITIVE); |
| 120 | + cn.set_col_names({"Name", "Age", "City"}); |
| 121 | + |
| 122 | + REQUIRE(cn.index_of("Name") == 0); |
| 123 | + REQUIRE(cn.index_of("Age") == 1); |
| 124 | + REQUIRE(cn.index_of("City") == 2); |
| 125 | +} |
| 126 | + |
| 127 | +TEST_CASE("ColNames - case-insensitive missing column returns CSV_NOT_FOUND", "[col_names][case_insensitive]") { |
| 128 | + ColNames cn; |
| 129 | + cn.set_policy(ColumnNamePolicy::CASE_INSENSITIVE); |
| 130 | + cn.set_col_names({"Name", "Age"}); |
| 131 | + |
| 132 | + REQUIRE(cn.index_of("missing") == CSV_NOT_FOUND); |
| 133 | + REQUIRE(cn.index_of("") == CSV_NOT_FOUND); |
| 134 | +} |
| 135 | + |
| 136 | +TEST_CASE("ColNames - case-insensitive get_col_names preserves original casing", "[col_names][case_insensitive]") { |
| 137 | + // The stored names should be in their original form even under CI policy. |
| 138 | + // The lowercase transform is internal to the lookup map only. |
| 139 | + ColNames cn; |
| 140 | + cn.set_policy(ColumnNamePolicy::CASE_INSENSITIVE); |
| 141 | + cn.set_col_names({"ReportDt", "Unit", "Power"}); |
| 142 | + |
| 143 | + auto names = cn.get_col_names(); |
| 144 | + REQUIRE(names[0] == "ReportDt"); |
| 145 | + REQUIRE(names[1] == "Unit"); |
| 146 | + REQUIRE(names[2] == "Power"); |
| 147 | +} |
| 148 | + |
| 149 | +TEST_CASE("ColNames - case-insensitive operator[] preserves original casing", "[col_names][case_insensitive]") { |
| 150 | + ColNames cn; |
| 151 | + cn.set_policy(ColumnNamePolicy::CASE_INSENSITIVE); |
| 152 | + cn.set_col_names({"ReportDt", "Unit"}); |
| 153 | + |
| 154 | + REQUIRE(cn[0] == "ReportDt"); |
| 155 | + REQUIRE(cn[1] == "Unit"); |
| 156 | +} |
| 157 | + |
| 158 | +TEST_CASE("ColNames - policy must be set before set_col_names to take effect", "[col_names][case_insensitive]") { |
| 159 | + // set_col_names called BEFORE set_policy: map is built with exact keys, |
| 160 | + // so CI lookup will not work. |
| 161 | + ColNames cn({"Name", "Age"}); // policy is EXACT at this point |
| 162 | + cn.set_policy(ColumnNamePolicy::CASE_INSENSITIVE); |
| 163 | + |
| 164 | + // The map was built with exact keys so lowercase query won't find anything. |
| 165 | + REQUIRE(cn.index_of("name") == CSV_NOT_FOUND); |
| 166 | + |
| 167 | + // After rebuilding the map, CI works. |
| 168 | + cn.set_col_names(cn.get_col_names()); |
| 169 | + REQUIRE(cn.index_of("name") == 0); |
| 170 | +} |
| 171 | + |
| 172 | +// ============================================================ |
| 173 | +// Edge cases |
| 174 | +// ============================================================ |
| 175 | + |
| 176 | +TEST_CASE("ColNames - empty column name list", "[col_names][edge_cases]") { |
| 177 | + ColNames cn; |
| 178 | + cn.set_col_names({}); |
| 179 | + |
| 180 | + REQUIRE(cn.empty()); |
| 181 | + REQUIRE(cn.size() == 0); |
| 182 | + REQUIRE(cn.index_of("anything") == CSV_NOT_FOUND); |
| 183 | +} |
| 184 | + |
| 185 | +TEST_CASE("ColNames - column name that is an empty string", "[col_names][edge_cases]") { |
| 186 | + ColNames cn({"", "B", "C"}); |
| 187 | + REQUIRE(cn.index_of("") == 0); |
| 188 | + REQUIRE(cn.index_of("B") == 1); |
| 189 | +} |
| 190 | + |
| 191 | +TEST_CASE("ColNames - duplicate column names: last index wins", "[col_names][edge_cases]") { |
| 192 | + // When header has duplicate names the last occurrence wins in the hash map. |
| 193 | + // This documents current behavior rather than prescribing it. |
| 194 | + ColNames cn({"dup", "other", "dup"}); |
| 195 | + REQUIRE(cn.index_of("dup") == 2); |
| 196 | + REQUIRE(cn.index_of("other") == 1); |
| 197 | +} |
0 commit comments