168 lines
6.5 KiB
C++
168 lines
6.5 KiB
C++
|
|
// Step 490: Threat Model Integration Tests (12 tests)
|
||
|
|
|
||
|
|
#include "ThreatModelIntegration.h"
|
||
|
|
|
||
|
|
#include <iostream>
|
||
|
|
#include <string>
|
||
|
|
|
||
|
|
static int passed = 0, failed = 0;
|
||
|
|
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||
|
|
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||
|
|
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||
|
|
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||
|
|
|
||
|
|
static ThreatModelInput baseInput() {
|
||
|
|
ThreatModelInput in;
|
||
|
|
in.moduleName = "api";
|
||
|
|
in.boundaries = {{"external-api", "high"}, {"database", "medium"}, {"internal", "low"}};
|
||
|
|
return in;
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_threat_model_annotation_emitted_at_module_level() {
|
||
|
|
TEST(threat_model_annotation_emitted_at_module_level);
|
||
|
|
auto out = ThreatModelIntegration::analyze(baseInput());
|
||
|
|
bool found = false;
|
||
|
|
for (const auto& a : out.requiredAnnotations) {
|
||
|
|
if (a.find("@ThreatModel(module=api)") != std::string::npos) found = true;
|
||
|
|
}
|
||
|
|
CHECK(found, "missing module threat model annotation");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_trust_boundaries_are_included_in_overlay_data() {
|
||
|
|
TEST(trust_boundaries_are_included_in_overlay_data);
|
||
|
|
auto out = ThreatModelIntegration::analyze(baseInput());
|
||
|
|
CHECK(out.overlayNodes.size() == 3, "overlay node count mismatch");
|
||
|
|
CHECK(out.overlayNodes[0].find("external-api:high") != std::string::npos, "missing boundary node");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_cross_boundary_flow_without_validation_emits_e1300() {
|
||
|
|
TEST(cross_boundary_flow_without_validation_emits_e1300);
|
||
|
|
auto in = baseInput();
|
||
|
|
in.flows = {{"external-api", "database", false, ""}};
|
||
|
|
auto out = ThreatModelIntegration::analyze(in);
|
||
|
|
CHECK(!out.diagnostics.empty(), "expected diagnostics");
|
||
|
|
CHECK(out.diagnostics[0].code == 1300, "expected E1300 diagnostic");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_boundary_flow_with_raw_validation_emits_e1301() {
|
||
|
|
TEST(boundary_flow_with_raw_validation_emits_e1301);
|
||
|
|
auto in = baseInput();
|
||
|
|
in.flows = {{"external-api", "database", true, "raw"}};
|
||
|
|
auto out = ThreatModelIntegration::analyze(in);
|
||
|
|
CHECK(!out.diagnostics.empty(), "expected diagnostics");
|
||
|
|
CHECK(out.diagnostics[0].code == 1301, "expected E1301 diagnostic");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_sanitized_boundary_flow_avoids_validation_diagnostic() {
|
||
|
|
TEST(sanitized_boundary_flow_avoids_validation_diagnostic);
|
||
|
|
auto in = baseInput();
|
||
|
|
in.flows = {{"external-api", "database", true, "sanitized"}};
|
||
|
|
auto out = ThreatModelIntegration::analyze(in);
|
||
|
|
CHECK(out.diagnostics.empty(), "did not expect diagnostics for sanitized flow");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_unknown_validation_kind_emits_e1303() {
|
||
|
|
TEST(unknown_validation_kind_emits_e1303);
|
||
|
|
auto in = baseInput();
|
||
|
|
in.flows = {{"external-api", "database", true, "custom"}};
|
||
|
|
auto out = ThreatModelIntegration::analyze(in);
|
||
|
|
CHECK(!out.diagnostics.empty(), "expected diagnostics");
|
||
|
|
CHECK(out.diagnostics[0].code == 1303, "expected E1303 diagnostic");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_no_boundaries_emits_e1302() {
|
||
|
|
TEST(no_boundaries_emits_e1302);
|
||
|
|
ThreatModelInput in;
|
||
|
|
in.moduleName = "api";
|
||
|
|
in.flows = {{"external-api", "database", false, ""}};
|
||
|
|
auto out = ThreatModelIntegration::analyze(in);
|
||
|
|
bool found = false;
|
||
|
|
for (const auto& d : out.diagnostics) if (d.code == 1302) found = true;
|
||
|
|
CHECK(found, "expected E1302 for missing boundaries");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_data_flow_annotation_is_emitted_for_crossing() {
|
||
|
|
TEST(data_flow_annotation_is_emitted_for_crossing);
|
||
|
|
auto in = baseInput();
|
||
|
|
in.flows = {{"external-api", "database", true, "sanitized"}};
|
||
|
|
auto out = ThreatModelIntegration::analyze(in);
|
||
|
|
bool found = false;
|
||
|
|
for (const auto& a : out.requiredAnnotations) {
|
||
|
|
if (a.find("@DataFlow(external-api->database)") != std::string::npos) found = true;
|
||
|
|
}
|
||
|
|
CHECK(found, "expected @DataFlow annotation");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_missing_validation_adds_input_validation_required_annotation() {
|
||
|
|
TEST(missing_validation_adds_input_validation_required_annotation);
|
||
|
|
auto in = baseInput();
|
||
|
|
in.flows = {{"user-input", "internal", false, ""}};
|
||
|
|
auto out = ThreatModelIntegration::analyze(in);
|
||
|
|
bool found = false;
|
||
|
|
for (const auto& a : out.requiredAnnotations) {
|
||
|
|
if (a == "@InputValidation(required)") found = true;
|
||
|
|
}
|
||
|
|
CHECK(found, "expected @InputValidation(required)");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_internal_same_boundary_flow_does_not_trigger_e1300() {
|
||
|
|
TEST(internal_same_boundary_flow_does_not_trigger_e1300);
|
||
|
|
auto in = baseInput();
|
||
|
|
in.flows = {{"internal", "internal", false, ""}};
|
||
|
|
auto out = ThreatModelIntegration::analyze(in);
|
||
|
|
for (const auto& d : out.diagnostics) {
|
||
|
|
CHECK(d.code != 1300, "E1300 should not be emitted for non-crossing flow");
|
||
|
|
}
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_overlay_note_is_present() {
|
||
|
|
TEST(overlay_note_is_present);
|
||
|
|
auto out = ThreatModelIntegration::analyze(baseInput());
|
||
|
|
CHECK(!out.notes.empty(), "expected notes");
|
||
|
|
CHECK(out.notes.back().find("overlay") != std::string::npos, "missing overlay note");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
void test_empty_flows_add_note_but_not_false_positive_diagnostic() {
|
||
|
|
TEST(empty_flows_add_note_but_not_false_positive_diagnostic);
|
||
|
|
auto in = baseInput();
|
||
|
|
auto out = ThreatModelIntegration::analyze(in);
|
||
|
|
bool note = false;
|
||
|
|
for (const auto& n : out.notes) {
|
||
|
|
if (n.find("No data flows") != std::string::npos) note = true;
|
||
|
|
}
|
||
|
|
CHECK(note, "expected no-data-flow note");
|
||
|
|
PASS();
|
||
|
|
}
|
||
|
|
|
||
|
|
int main() {
|
||
|
|
std::cout << "Step 490: Threat Model Integration Tests\n";
|
||
|
|
|
||
|
|
test_threat_model_annotation_emitted_at_module_level(); // 1
|
||
|
|
test_trust_boundaries_are_included_in_overlay_data(); // 2
|
||
|
|
test_cross_boundary_flow_without_validation_emits_e1300(); // 3
|
||
|
|
test_boundary_flow_with_raw_validation_emits_e1301(); // 4
|
||
|
|
test_sanitized_boundary_flow_avoids_validation_diagnostic(); // 5
|
||
|
|
test_unknown_validation_kind_emits_e1303(); // 6
|
||
|
|
test_no_boundaries_emits_e1302(); // 7
|
||
|
|
test_data_flow_annotation_is_emitted_for_crossing(); // 8
|
||
|
|
test_missing_validation_adds_input_validation_required_annotation(); // 9
|
||
|
|
test_internal_same_boundary_flow_does_not_trigger_e1300(); // 10
|
||
|
|
test_overlay_note_is_present(); // 11
|
||
|
|
test_empty_flows_add_note_but_not_false_positive_diagnostic(); // 12
|
||
|
|
|
||
|
|
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
||
|
|
<< " passed\n";
|
||
|
|
return failed == 0 ? 0 : 1;
|
||
|
|
}
|