@@ -1625,6 +1625,164 @@ void searchAppVulnerabilities_should_filter_by_session_metadata_case_insensitive
16251625 verify (mockContrastSDK ).getTraces (eq (TEST_ORG_ID ), eq (TEST_APP_ID ), any (), any ());
16261626 }
16271627
1628+ @ Test
1629+ void searchAppVulnerabilities_should_treat_null_sessionMetadataValue_as_wildcard_match_any_value ()
1630+ throws Exception {
1631+ // Given - 3 traces with different values for same metadata name
1632+ var mockTraces = mock (Traces .class );
1633+ var traces = new ArrayList <com .contrastsecurity .models .Trace >();
1634+
1635+ // Trace 1: has "branch" metadata with value "main"
1636+ Trace trace1 = mock ();
1637+ when (trace1 .getTitle ()).thenReturn ("SQL Injection vulnerability" );
1638+ when (trace1 .getRule ()).thenReturn ("sql-injection" );
1639+ when (trace1 .getUuid ()).thenReturn ("uuid-1" );
1640+ when (trace1 .getSeverity ()).thenReturn ("HIGH" );
1641+ when (trace1 .getLastTimeSeen ()).thenReturn (System .currentTimeMillis ());
1642+
1643+ var sessionMetadata1 = mock (com .contrastsecurity .models .SessionMetadata .class );
1644+ var metadataItem1 = mock (com .contrastsecurity .models .MetadataItem .class );
1645+ when (metadataItem1 .getDisplayLabel ()).thenReturn ("branch" );
1646+ // No getValue() stub needed - wildcard test doesn't check values
1647+ when (sessionMetadata1 .getMetadata ()).thenReturn (List .of (metadataItem1 ));
1648+ when (trace1 .getSessionMetadata ()).thenReturn (List .of (sessionMetadata1 ));
1649+ traces .add (trace1 );
1650+
1651+ // Trace 2: has "branch" metadata with value "develop"
1652+ Trace trace2 = mock ();
1653+ when (trace2 .getTitle ()).thenReturn ("XSS vulnerability" );
1654+ when (trace2 .getRule ()).thenReturn ("xss" );
1655+ when (trace2 .getUuid ()).thenReturn ("uuid-2" );
1656+ when (trace2 .getSeverity ()).thenReturn ("MEDIUM" );
1657+ when (trace2 .getLastTimeSeen ()).thenReturn (System .currentTimeMillis ());
1658+
1659+ var sessionMetadata2 = mock (com .contrastsecurity .models .SessionMetadata .class );
1660+ var metadataItem2 = mock (com .contrastsecurity .models .MetadataItem .class );
1661+ when (metadataItem2 .getDisplayLabel ()).thenReturn ("branch" );
1662+ // No getValue() stub needed - wildcard test doesn't check values
1663+ when (sessionMetadata2 .getMetadata ()).thenReturn (List .of (metadataItem2 ));
1664+ when (trace2 .getSessionMetadata ()).thenReturn (List .of (sessionMetadata2 ));
1665+ traces .add (trace2 );
1666+
1667+ // Trace 3: has "environment" metadata (different name, should not match)
1668+ Trace trace3 = mock ();
1669+ when (trace3 .getTitle ()).thenReturn ("Command Injection vulnerability" );
1670+ when (trace3 .getRule ()).thenReturn ("cmd-injection" );
1671+ when (trace3 .getUuid ()).thenReturn ("uuid-3" );
1672+ when (trace3 .getSeverity ()).thenReturn ("CRITICAL" );
1673+ when (trace3 .getLastTimeSeen ()).thenReturn (System .currentTimeMillis ());
1674+
1675+ var sessionMetadata3 = mock (com .contrastsecurity .models .SessionMetadata .class );
1676+ var metadataItem3 = mock (com .contrastsecurity .models .MetadataItem .class );
1677+ when (metadataItem3 .getDisplayLabel ()).thenReturn ("environment" );
1678+ // No getValue() stub needed - name doesn't match so value never checked
1679+ when (sessionMetadata3 .getMetadata ()).thenReturn (List .of (metadataItem3 ));
1680+ when (trace3 .getSessionMetadata ()).thenReturn (List .of (sessionMetadata3 ));
1681+ traces .add (trace3 );
1682+
1683+ when (mockTraces .getTraces ()).thenReturn (traces );
1684+
1685+ // Mock SDK to return all 3 traces
1686+ when (mockContrastSDK .getTraces (eq (TEST_ORG_ID ), eq (TEST_APP_ID ), any (), any ()))
1687+ .thenReturn (mockTraces );
1688+
1689+ // When - search with sessionMetadataName but NULL sessionMetadataValue (wildcard)
1690+ var result =
1691+ assessService .searchAppVulnerabilities (
1692+ TEST_APP_ID ,
1693+ 1 , // page
1694+ 50 , // pageSize
1695+ null ,
1696+ null ,
1697+ null ,
1698+ null ,
1699+ null ,
1700+ null ,
1701+ null ,
1702+ "branch" , // sessionMetadataName
1703+ null , // sessionMetadataValue = null (wildcard, match any value)
1704+ null );
1705+
1706+ // Then - should return traces 1 and 2 (both have "branch" metadata, regardless of value)
1707+ assertThat (result .items ()).hasSize (2 );
1708+ assertThat (result .items ().get (0 ).vulnID ()).isEqualTo ("uuid-1" );
1709+ assertThat (result .items ().get (1 ).vulnID ()).isEqualTo ("uuid-2" );
1710+ assertThat (result .totalItems ()).isEqualTo (2 );
1711+
1712+ // Verify SDK was called
1713+ verify (mockContrastSDK ).getTraces (eq (TEST_ORG_ID ), eq (TEST_APP_ID ), any (), any ());
1714+ }
1715+
1716+ @ Test
1717+ void searchAppVulnerabilities_should_handle_metadata_item_with_null_value () throws Exception {
1718+ // Given - traces with metadata items that have null values
1719+ var mockTraces = mock (Traces .class );
1720+ var traces = new ArrayList <com .contrastsecurity .models .Trace >();
1721+
1722+ // Trace 1: has "branch" metadata with NULL value
1723+ Trace trace1 = mock ();
1724+ when (trace1 .getTitle ()).thenReturn ("SQL Injection vulnerability" );
1725+ when (trace1 .getRule ()).thenReturn ("sql-injection" );
1726+ when (trace1 .getUuid ()).thenReturn ("uuid-1" );
1727+ when (trace1 .getSeverity ()).thenReturn ("HIGH" );
1728+ when (trace1 .getLastTimeSeen ()).thenReturn (System .currentTimeMillis ());
1729+
1730+ var sessionMetadata1 = mock (com .contrastsecurity .models .SessionMetadata .class );
1731+ var metadataItem1 = mock (com .contrastsecurity .models .MetadataItem .class );
1732+ when (metadataItem1 .getDisplayLabel ()).thenReturn ("branch" );
1733+ when (metadataItem1 .getValue ()).thenReturn (null ); // NULL value
1734+ when (sessionMetadata1 .getMetadata ()).thenReturn (List .of (metadataItem1 ));
1735+ when (trace1 .getSessionMetadata ()).thenReturn (List .of (sessionMetadata1 ));
1736+ traces .add (trace1 );
1737+
1738+ // Trace 2: has "branch" metadata with actual value "main"
1739+ Trace trace2 = mock ();
1740+ when (trace2 .getTitle ()).thenReturn ("XSS vulnerability" );
1741+ when (trace2 .getRule ()).thenReturn ("xss" );
1742+ when (trace2 .getUuid ()).thenReturn ("uuid-2" );
1743+ when (trace2 .getSeverity ()).thenReturn ("MEDIUM" );
1744+ when (trace2 .getLastTimeSeen ()).thenReturn (System .currentTimeMillis ());
1745+
1746+ var sessionMetadata2 = mock (com .contrastsecurity .models .SessionMetadata .class );
1747+ var metadataItem2 = mock (com .contrastsecurity .models .MetadataItem .class );
1748+ when (metadataItem2 .getDisplayLabel ()).thenReturn ("branch" );
1749+ when (metadataItem2 .getValue ()).thenReturn ("main" );
1750+ when (sessionMetadata2 .getMetadata ()).thenReturn (List .of (metadataItem2 ));
1751+ when (trace2 .getSessionMetadata ()).thenReturn (List .of (sessionMetadata2 ));
1752+ traces .add (trace2 );
1753+
1754+ when (mockTraces .getTraces ()).thenReturn (traces );
1755+
1756+ // Mock SDK to return both traces
1757+ when (mockContrastSDK .getTraces (eq (TEST_ORG_ID ), eq (TEST_APP_ID ), any (), any ()))
1758+ .thenReturn (mockTraces );
1759+
1760+ // When - search with specific value "main"
1761+ var result =
1762+ assessService .searchAppVulnerabilities (
1763+ TEST_APP_ID ,
1764+ 1 , // page
1765+ 50 , // pageSize
1766+ null ,
1767+ null ,
1768+ null ,
1769+ null ,
1770+ null ,
1771+ null ,
1772+ null ,
1773+ "branch" , // sessionMetadataName
1774+ "main" , // sessionMetadataValue = "main"
1775+ null );
1776+
1777+ // Then - should return only trace 2 (trace 1 has null value, doesn't match "main")
1778+ assertThat (result .items ()).hasSize (1 );
1779+ assertThat (result .items ().get (0 ).vulnID ()).isEqualTo ("uuid-2" );
1780+ assertThat (result .totalItems ()).isEqualTo (1 );
1781+
1782+ // Verify SDK was called and no NullPointerException occurred
1783+ verify (mockContrastSDK ).getTraces (eq (TEST_ORG_ID ), eq (TEST_APP_ID ), any (), any ());
1784+ }
1785+
16281786 @ Test
16291787 void searchAppVulnerabilities_should_pass_all_standard_filters_to_SDK () throws Exception {
16301788 // Given
0 commit comments