diff --git a/pytest_splunk_addon/fields_tests/test_generator.py b/pytest_splunk_addon/fields_tests/test_generator.py index 85374d4b2..46b4ef4bb 100644 --- a/pytest_splunk_addon/fields_tests/test_generator.py +++ b/pytest_splunk_addon/fields_tests/test_generator.py @@ -250,17 +250,19 @@ def generate_requirements_tests(self): } cim_fields = event.requirement_test_data.get("cim_fields", {}) + other_fields = event.requirement_test_data.get("other_fields", {}) + requirement_fields = {**cim_fields, **other_fields} - if cim_fields: - cim_fields = { + if requirement_fields: + requirement_fields = { field: value - for field, value in cim_fields.items() + for field, value in requirement_fields.items() if field not in exceptions } yield pytest.param( { "escaped_event": escaped_event, - "fields": cim_fields, + "fields": requirement_fields, "modinput_params": modinput_params, }, id=f"sample_name::{event.sample_name}::host::{event.metadata.get('host')}", diff --git a/pytest_splunk_addon/sample_generation/sample_stanza.py b/pytest_splunk_addon/sample_generation/sample_stanza.py index 55d7cfce9..941d24614 100644 --- a/pytest_splunk_addon/sample_generation/sample_stanza.py +++ b/pytest_splunk_addon/sample_generation/sample_stanza.py @@ -398,6 +398,16 @@ def populate_requirement_test_data(event): """ requirement_test_data = {} cim = event.get("cim") + other_mappings = event.get("other_mappings") + if other_mappings: + other_fields = {} + fields = other_mappings["field"] + if type(fields) == list: + for field in fields: + other_fields[field["@name"]] = field["@value"] + elif type(fields) == dict: + other_fields[fields["@name"]] = fields["@value"] + requirement_test_data["other_fields"] = other_fields if cim: requirement_test_data["cim_version"] = cim.get("@version", "latest") requirement_test_data["datamodels"] = cim.get("models") or {} diff --git a/tests/e2e/addons/TA_req_broken/samples/sample_modinput.xml b/tests/e2e/addons/TA_req_broken/samples/sample_modinput.xml index ccd341d48..20911af1f 100644 --- a/tests/e2e/addons/TA_req_broken/samples/sample_modinput.xml +++ b/tests/e2e/addons/TA_req_broken/samples/sample_modinput.xml @@ -102,4 +102,32 @@ + + + + + lab + + + + + + Authentication + + + + + + + + + + + src_user + + + + + + diff --git a/tests/e2e/addons/TA_transition_from_req/default/props.conf b/tests/e2e/addons/TA_transition_from_req/default/props.conf index 61c9b087a..ee1bf418e 100644 --- a/tests/e2e/addons/TA_transition_from_req/default/props.conf +++ b/tests/e2e/addons/TA_transition_from_req/default/props.conf @@ -10,4 +10,5 @@ FIELDALIAS-action = result AS action EVAL-app = "psa" FIELDALIAS-user = tester AS user FIELDALIAS-src = ip AS src -EVAL-status = case(action=="success", "PASS", action=="failure", "FAIL", 0==0, "OTHER") \ No newline at end of file +EVAL-status = case(action=="success", "PASS", action=="failure", "FAIL", 0==0, "OTHER") +EVAL-vendor_product = "Pytest Splunk Addon" \ No newline at end of file diff --git a/tests/e2e/addons/TA_transition_from_req/samples/sample_modinput.xml b/tests/e2e/addons/TA_transition_from_req/samples/sample_modinput.xml index 0f4a83286..1a1b2369a 100644 --- a/tests/e2e/addons/TA_transition_from_req/samples/sample_modinput.xml +++ b/tests/e2e/addons/TA_transition_from_req/samples/sample_modinput.xml @@ -26,5 +26,8 @@ src_user + + + \ No newline at end of file diff --git a/tests/e2e/constants.py b/tests/e2e/constants.py index 3e6be5345..403c1910b 100644 --- a/tests/e2e/constants.py +++ b/tests/e2e/constants.py @@ -798,6 +798,7 @@ "*test_splunk_app_req.py::Test_App::test_props_fields[test:data:1::field::status* PASSED*", "*test_splunk_app_req.py::Test_App::test_props_fields[test:data:1::field::tester* PASSED*", "*test_splunk_app_req.py::Test_App::test_props_fields[test:data:1::field::user* PASSED*", + "*test_splunk_app_req.py::Test_App::test_props_fields[test:data:1::field::vendor_product* PASSED*", "*test_splunk_app_req.py::Test_App::test_requirements_fields[sample_name::sample_modinput.xml::host::so1-4* PASSED*", "*test_splunk_app_req.py::Test_App::test_requirements_fields[sample_name::sample_modinput.xml::host::so1-5* PASSED*", "*test_splunk_app_req.py::Test_App::test_requirements_fields[sample_name::sample_modinput.xml::host::so1-6* PASSED*", @@ -811,6 +812,7 @@ "*test_splunk_app_req.py::Test_App::test_props_fields_no_dash_not_empty[test:data:1::field::status* PASSED*", "*test_splunk_app_req.py::Test_App::test_props_fields_no_dash_not_empty[test:data:1::field::tester* PASSED*", "*test_splunk_app_req.py::Test_App::test_props_fields_no_dash_not_empty[test:data:1::field::user* PASSED*", + "*test_splunk_app_req.py::Test_App::test_props_fields_no_dash_not_empty[test:data:1::field::vendor_product* PASSED*", "*test_splunk_app_req.py::Test_App::test_datamodels[Authentication::sample_name::sample_modinput.xml::host::so1-4* PASSED*", "*test_splunk_app_req.py::Test_App::test_datamodels[Authentication::sample_name::sample_modinput.xml::host::so1-5* PASSED*", "*test_splunk_app_req.py::Test_App::test_datamodels[Authentication::sample_name::sample_modinput.xml::host::so1-6* PASSED*", @@ -836,6 +838,7 @@ "*test_splunk_app_req_broken.py::Test_App::test_indextime_time[req:test:broken::so11* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_indextime_time[req:test:broken::so12* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_indextime_time[req:test:broken::so13* PASSED*", + "*test_splunk_app_req_broken.py::Test_App::test_indextime_time[req:test:broken::so14* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_indextime_line_breaker[juniper:junos:firewall::syslog.xml* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_indextime_line_breaker[req:test:broken::sample_modinput.xml* PASSED*", '*test_splunk_app_req_broken.py::Test_App::test_cim_required_fields[eventtype="net"::All_Traffic* PASSED*', @@ -882,6 +885,7 @@ "*test_splunk_app_req_broken.py::Test_App::test_cim_fields_recommended[Authentication-::sample_name::sample_modinput.xml::host::so10* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_cim_fields_recommended[Authentication-::sample_name::sample_modinput.xml::host::so12* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_cim_fields_recommended[Authentication-::sample_name::sample_modinput.xml::host::so13* PASSED*", + "*test_splunk_app_req_broken.py::Test_App::test_cim_fields_recommended[Authentication-::sample_name::sample_modinput.xml::host::so14* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_cim_fields_recommended[Network_Traffic-::sample_name::syslog.xml::host::10.0.0.30* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_cim_fields_recommended[Network_Traffic-::sample_name::syslog.xml::host::10.0.0.31* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_splunk_internal_errors PASSED*", @@ -897,6 +901,7 @@ "*test_splunk_app_req_broken.py::Test_App::test_datamodels[Authentication::sample_name::sample_modinput.xml::host::so11* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_datamodels[Authentication::sample_name::sample_modinput.xml::host::so12* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_datamodels[Authentication::sample_name::sample_modinput.xml::host::so13* PASSED*", + "*test_splunk_app_req_broken.py::Test_App::test_datamodels[Authentication::sample_name::sample_modinput.xml::host::so14* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_datamodels[Network_Traffic::sample_name::syslog.xml::host::10.0.0.30* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_eventtype[eventtype::net* PASSED*", "*test_splunk_app_req_broken.py::Test_App::test_eventtype[eventtype::test_auth* PASSED*", @@ -913,6 +918,7 @@ '*test_splunk_app_req_broken.py::Test_App::test_cim_required_fields[eventtype="net"::All_Traffic::src_zone* FAILED*', '*test_splunk_app_req_broken.py::Test_App::test_cim_required_fields[eventtype="net"::Blocked_Traffic* FAILED*', "*test_splunk_app_req_broken.py::Test_App::test_requirements_fields[sample_name::sample_modinput.xml::host::so13* FAILED*", + "*test_splunk_app_req_broken.py::Test_App::test_requirements_fields[sample_name::sample_modinput.xml::host::so14* FAILED*", "*test_splunk_app_req_broken.py::Test_App::test_cim_fields_recommended[Authentication-::sample_name::sample_modinput.xml::host::so11* FAILED*", "*test_splunk_app_req_broken.py::Test_App::test_datamodels[Network_Traffic::sample_name::syslog.xml::host::10.0.0.31* FAILED*", ] diff --git a/tests/unit/tests_standard_lib/test_fields_tests/test_test_generator.py b/tests/unit/tests_standard_lib/test_fields_tests/test_test_generator.py index c0508166c..e57daefa5 100644 --- a/tests/unit/tests_standard_lib/test_fields_tests/test_test_generator.py +++ b/tests/unit/tests_standard_lib/test_fields_tests/test_test_generator.py @@ -3,6 +3,8 @@ from pytest_splunk_addon.fields_tests.test_generator import ( FieldTestGenerator, ) +from pytest_splunk_addon.sample_generation.sample_event import SampleEvent +from pytest_splunk_addon.utilities import xml_event_parser def field_1(): @@ -71,6 +73,14 @@ def test_field_test_generator_instantiation(addon_parser_mock): "splunk_searchtime_fields_savedsearches", "GENERATE_SAVEDSEARCHES_TESTS_RETURN_VALUE", ), + ( + "splunk_searchtime_fields_requirements", + "GENERATE_REQUIREMENT_TESTS_RETURN_VALUE", + ), + ( + "splunk_searchtime_fields_datamodels", + "GENERATE_REQUIREMENT_DATAMODEL_TESTS_RETURN_VALUE", + ), ], ) def test_generate_tests(addon_parser_mock, fixture_name, expected_ouptput): @@ -90,6 +100,14 @@ def test_generate_tests(addon_parser_mock, fixture_name, expected_ouptput): FieldTestGenerator, "generate_savedsearches_tests", return_value=(["GENERATE_SAVEDSEARCHES_TESTS_RETURN_VALUE"]), + ), patch.object( + FieldTestGenerator, + "generate_requirements_tests", + return_value=(["GENERATE_REQUIREMENT_TESTS_RETURN_VALUE"]), + ), patch.object( + FieldTestGenerator, + "generate_requirements_datamodels_tests", + return_value=(["GENERATE_REQUIREMENT_DATAMODEL_TESTS_RETURN_VALUE"]), ): assert list( FieldTestGenerator( @@ -391,3 +409,190 @@ def test_generate_field_tests( ) assert out == expected_output assert param_mock.call_count == len(expected_output) + + +@pytest.mark.parametrize( + "tokenised_events, expected_output", + [ + ( + [ + SampleEvent( + event_string="escaped_event", + metadata={ + "input_type": "modinput", + "sourcetype_to_search": "dummy_sourcetype", + "host": "dummy_host", + }, + sample_name="file1.xml", + requirement_test_data={ + "cim_fields": { + "dest": "192.168.0.1", + "severity": "low", + "signature_id": "405001", + "src": "192.168.0.1", + "type": "event", + }, + "exceptions": {"mane_1": "value_1", "dest": "192.168.0.1"}, + "other_fields": { + "vendor_product": "Pytest Splunk Addon", + "target_users": "dummy.user@splunk.com", + }, + }, + ), + SampleEvent( + event_string="escaped_event", + metadata={ + "input_type": "syslog_tcp", + "sourcetype_to_search": "dummy_sourcetype", + "host": "dummy_host_syslog", + }, + sample_name="file1.xml", + requirement_test_data={}, + ), + SampleEvent( + event_string="escaped_event", + metadata={ + "input_type": "syslog_tcp", + "sourcetype_to_search": "dummy_sourcetype", + "host": "dummy_host_syslog", + }, + sample_name="file1.xml", + requirement_test_data={ + "cim_fields": { + "src": "192.168.0.1", + "type": "event", + }, + "exceptions": {}, + "other_fields": { + "vendor_product": "Pytest Splunk Addon", + "target_users": "dummy.user@splunk.com", + }, + }, + ), + ], + [ + ( + { + "escaped_event": "escaped_event", + "fields": { + "severity": "low", + "signature_id": "405001", + "src": "192.168.0.1", + "type": "event", + "vendor_product": "Pytest Splunk Addon", + "target_users": "dummy.user@splunk.com", + }, + "modinput_params": {"sourcetype": "dummy_sourcetype"}, + }, + "sample_name::file1.xml::host::dummy_host", + ), + ( + { + "escaped_event": "escaped_event", + "fields": { + "src": "192.168.0.1", + "type": "event", + "vendor_product": "Pytest Splunk Addon", + "target_users": "dummy.user@splunk.com", + }, + "modinput_params": {"sourcetype": "dummy_sourcetype"}, + }, + "sample_name::file1.xml::host::dummy_host_syslog", + ), + ], + ), + ], +) +def test_generate_requirement_tests(tokenised_events, expected_output): + with patch.object( + xml_event_parser, "strip_syslog_header", return_value="escaped_event" + ), patch.object( + xml_event_parser, "escape_char_event", return_value="escaped_event" + ), patch.object( + pytest, "param", side_effect=lambda x, id: (x, id) + ) as param_mock: + out = list( + FieldTestGenerator( + "app_path", + tokenised_events, + "field_bank", + ).generate_requirements_tests() + ) + assert out == expected_output + assert param_mock.call_count == len(expected_output) + + +@pytest.mark.parametrize( + "tokenised_events, expected_output", + [ + ( + [ + SampleEvent( + event_string="escaped_event", + metadata={ + "input_type": "modinput", + "sourcetype_to_search": "dummy_sourcetype", + "host": "dummy_host", + }, + sample_name="file1.xml", + requirement_test_data={"datamodels": {"model": "Alerts"}}, + ), + SampleEvent( + event_string="escaped_event", + metadata={ + "input_type": "syslog_tcp", + "sourcetype_to_search": "dummy_sourcetype", + "host": "dummy_host_syslog", + }, + sample_name="file1.xml", + requirement_test_data={}, + ), + SampleEvent( + event_string="escaped_event", + metadata={ + "input_type": "syslog_tcp", + "sourcetype_to_search": "dummy_sourcetype", + "host": "dummy_host_syslog", + }, + sample_name="file1.xml", + requirement_test_data={ + "datamodels": {"model": ["Change", "Account Management"]} + }, + ), + ], + [ + ( + { + "datamodels": ["Alerts"], + "stanza": "escaped_event", + }, + "Alerts::sample_name::file1.xml::host::dummy_host", + ), + ( + { + "datamodels": ["Change", "Account_Management"], + "stanza": "escaped_event", + }, + "Change-Account_Management::sample_name::file1.xml::host::dummy_host_syslog", + ), + ], + ), + ], +) +def test_generate_requirement_datamodel_tests(tokenised_events, expected_output): + with patch.object( + xml_event_parser, "strip_syslog_header", return_value="escaped_event" + ), patch.object( + xml_event_parser, "escape_char_event", return_value="escaped_event" + ), patch.object( + pytest, "param", side_effect=lambda x, id: (x, id) + ) as param_mock: + out = list( + FieldTestGenerator( + "app_path", + tokenised_events, + "field_bank", + ).generate_requirements_datamodels_tests() + ) + assert out == expected_output + assert param_mock.call_count == len(expected_output) diff --git a/tests/unit/tests_standard_lib/tests_sample_generation/test_sample_stanza.py b/tests/unit/tests_standard_lib/tests_sample_generation/test_sample_stanza.py index e8429f7b2..92da36e37 100644 --- a/tests/unit/tests_standard_lib/tests_sample_generation/test_sample_stanza.py +++ b/tests/unit/tests_standard_lib/tests_sample_generation/test_sample_stanza.py @@ -358,12 +358,63 @@ def test_break_events_exception(self, sample_stanza, caplog): "missing_recommended_fields": [], }, ), + ( + { + "cim": { + "@version": "4.20.2", + "models": {"model": "Alerts"}, + "cim_fields": { + "field": [ + {"@name": "dest", "@value": "192.168.0.1"}, + {"@name": "signature_id", "@value": "405001"}, + {"@name": "severity", "@value": "low"}, + {"@name": "src", "@value": "192.168.0.1"}, + {"@name": "type", "@value": "event"}, + ] + }, + "missing_recommended_fields": { + "field": ["app", "id", "user", "user_name"] + }, + "exceptions": {}, + }, + "other_mappings": { + "field": [ + { + "@name": "vendor_product", + "@value": "Pytest Splunk Addon", + }, + { + "@name": "target_users", + "@value": "dummy.user@splunk.com", + }, + ] + }, + }, + { + "cim_version": "4.20.2", + "cim_fields": { + "dest": "192.168.0.1", + "severity": "low", + "signature_id": "405001", + "src": "192.168.0.1", + "type": "event", + }, + "datamodels": {"model": "Alerts"}, + "exceptions": {}, + "missing_recommended_fields": ["app", "id", "user", "user_name"], + "other_fields": { + "vendor_product": "Pytest Splunk Addon", + "target_users": "dummy.user@splunk.com", + }, + }, + ), ], ids=[ "event-empty-directory", "event-no-cim", "event-full-cim", "event-with-exceptions", + "event-with-other-mappings", ], ) def test_populate_requirement_test_data(self, sample_stanza, event, expected):