@@ -83,7 +83,7 @@ describe('AttackHistory', () => {
8383
8484 expect ( screen . getByText ( 'Attack History' ) ) . toBeInTheDocument ( )
8585 expect ( screen . getByTestId ( 'refresh-btn' ) ) . toBeInTheDocument ( )
86- expect ( screen . getByTestId ( 'attack-class -filter' ) ) . toBeInTheDocument ( )
86+ expect ( screen . getByTestId ( 'attack-type -filter' ) ) . toBeInTheDocument ( )
8787 expect ( screen . getByTestId ( 'outcome-filter' ) ) . toBeInTheDocument ( )
8888 expect ( screen . getByTestId ( 'converter-filter' ) ) . toBeInTheDocument ( )
8989 expect ( screen . getByTestId ( 'operator-filter' ) ) . toBeInTheDocument ( )
@@ -656,7 +656,7 @@ describe('AttackHistory', () => {
656656 expect ( screen . queryByTestId ( 'reset-filters-btn' ) ) . not . toBeInTheDocument ( )
657657 } )
658658
659- it ( 'should call onFiltersChange with attackClass when attack type filter is selected' , async ( ) => {
659+ it ( 'should call onFiltersChange with attackTypes when attack type filter is selected' , async ( ) => {
660660 mockedAttacksApi . listAttacks . mockResolvedValue ( {
661661 items : [ ] ,
662662 pagination : { limit : 25 , has_more : false } ,
@@ -684,8 +684,8 @@ describe('AttackHistory', () => {
684684 expect ( mockedAttacksApi . getAttackOptions ) . toHaveBeenCalled ( )
685685 } )
686686
687- // Open the attack class dropdown and select an option
688- const attackDropdown = screen . getByTestId ( 'attack-class -filter' )
687+ // Open the attack type dropdown and select an option
688+ const attackDropdown = screen . getByTestId ( 'attack-type -filter' )
689689 fireEvent . click ( attackDropdown )
690690
691691 await waitFor ( ( ) => {
@@ -695,7 +695,7 @@ describe('AttackHistory', () => {
695695 fireEvent . click ( screen . getByText ( 'CrescendoAttack' ) )
696696
697697 expect ( onFiltersChange ) . toHaveBeenCalledWith (
698- expect . objectContaining ( { attackClass : 'CrescendoAttack' } )
698+ expect . objectContaining ( { attackTypes : [ 'CrescendoAttack' ] } )
699699 )
700700 } )
701701
@@ -767,7 +767,7 @@ describe('AttackHistory', () => {
767767 fireEvent . click ( screen . getByText ( 'Base64Converter' ) )
768768
769769 expect ( onFiltersChange ) . toHaveBeenCalledWith (
770- expect . objectContaining ( { converter : 'Base64Converter' } )
770+ expect . objectContaining ( { converter : [ 'Base64Converter' ] } )
771771 )
772772 } )
773773
@@ -808,7 +808,7 @@ describe('AttackHistory', () => {
808808 fireEvent . click ( screen . getByText ( 'alice' ) )
809809
810810 expect ( onFiltersChange ) . toHaveBeenCalledWith (
811- expect . objectContaining ( { operator : 'alice' } )
811+ expect . objectContaining ( { operator : [ 'alice' ] } )
812812 )
813813 } )
814814
@@ -849,7 +849,7 @@ describe('AttackHistory', () => {
849849 fireEvent . click ( screen . getByText ( 'op_alpha' ) )
850850
851851 expect ( onFiltersChange ) . toHaveBeenCalledWith (
852- expect . objectContaining ( { operation : 'op_alpha' } )
852+ expect . objectContaining ( { operation : [ 'op_alpha' ] } )
853853 )
854854 } )
855855
@@ -900,9 +900,9 @@ describe('AttackHistory', () => {
900900 const onFiltersChange = jest . fn ( )
901901 const activeFilters = {
902902 ...DEFAULT_HISTORY_FILTERS ,
903- attackClass : 'CrescendoAttack' ,
903+ attackTypes : [ 'CrescendoAttack' ] ,
904904 outcome : 'success' ,
905- operator : 'alice' ,
905+ operator : [ 'alice' ] ,
906906 }
907907
908908 render (
@@ -961,4 +961,129 @@ describe('AttackHistory', () => {
961961 expect . objectContaining ( { otherLabels : expect . any ( Array ) , labelSearchText : '' } )
962962 )
963963 } )
964+
965+ it ( 'should forward multi-select attackTypes as attack_types array to the API' , async ( ) => {
966+ mockedAttacksApi . listAttacks . mockResolvedValue ( {
967+ items : [ ] ,
968+ pagination : { limit : 25 , has_more : false } ,
969+ } )
970+ mockedAttacksApi . getAttackOptions . mockResolvedValue ( { attack_types : [ ] } )
971+ mockedAttacksApi . getConverterOptions . mockResolvedValue ( { converter_types : [ ] } )
972+ mockedLabelsApi . getLabels . mockResolvedValue ( { source : 'attacks' , labels : { } } )
973+
974+ const filters = {
975+ ...DEFAULT_HISTORY_FILTERS ,
976+ attackTypes : [ 'CrescendoAttack' , 'RedTeamingAttack' ] ,
977+ }
978+
979+ render (
980+ < TestWrapper >
981+ < AttackHistory { ...defaultProps } filters = { filters } />
982+ </ TestWrapper >
983+ )
984+
985+ await waitFor ( ( ) => {
986+ expect ( mockedAttacksApi . listAttacks ) . toHaveBeenCalled ( )
987+ } )
988+
989+ expect ( mockedAttacksApi . listAttacks ) . toHaveBeenCalledWith (
990+ expect . objectContaining ( { attack_types : [ 'CrescendoAttack' , 'RedTeamingAttack' ] } )
991+ )
992+ } )
993+
994+ it ( 'should forward hasConverters=false without converter_types or converter_types_match' , async ( ) => {
995+ mockedAttacksApi . listAttacks . mockResolvedValue ( {
996+ items : [ ] ,
997+ pagination : { limit : 25 , has_more : false } ,
998+ } )
999+ mockedAttacksApi . getAttackOptions . mockResolvedValue ( { attack_types : [ ] } )
1000+ mockedAttacksApi . getConverterOptions . mockResolvedValue ( { converter_types : [ ] } )
1001+ mockedLabelsApi . getLabels . mockResolvedValue ( { source : 'attacks' , labels : { } } )
1002+
1003+ const filters = {
1004+ ...DEFAULT_HISTORY_FILTERS ,
1005+ hasConverters : false ,
1006+ converter : [ ] ,
1007+ }
1008+
1009+ render (
1010+ < TestWrapper >
1011+ < AttackHistory { ...defaultProps } filters = { filters } />
1012+ </ TestWrapper >
1013+ )
1014+
1015+ await waitFor ( ( ) => {
1016+ expect ( mockedAttacksApi . listAttacks ) . toHaveBeenCalled ( )
1017+ } )
1018+
1019+ const callArgs = mockedAttacksApi . listAttacks . mock . calls [ 0 ] [ 0 ]
1020+ expect ( callArgs ) . toEqual ( expect . objectContaining ( { has_converters : false } ) )
1021+ expect ( callArgs ) . not . toHaveProperty ( 'converter_types' )
1022+ expect ( callArgs ) . not . toHaveProperty ( 'converter_types_match' )
1023+ } )
1024+
1025+ it ( 'should only send converter_types_match when two or more converters are selected' , async ( ) => {
1026+ mockedAttacksApi . listAttacks . mockResolvedValue ( {
1027+ items : [ ] ,
1028+ pagination : { limit : 25 , has_more : false } ,
1029+ } )
1030+ mockedAttacksApi . getAttackOptions . mockResolvedValue ( { attack_types : [ ] } )
1031+ mockedAttacksApi . getConverterOptions . mockResolvedValue ( { converter_types : [ ] } )
1032+ mockedLabelsApi . getLabels . mockResolvedValue ( { source : 'attacks' , labels : { } } )
1033+
1034+ // Case 1: single converter → converter_types_match is NOT sent
1035+ const singleFilters = {
1036+ ...DEFAULT_HISTORY_FILTERS ,
1037+ converter : [ 'Base64Converter' ] ,
1038+ converterMatchMode : 'all' as const ,
1039+ }
1040+
1041+ const { unmount } = render (
1042+ < TestWrapper >
1043+ < AttackHistory { ...defaultProps } filters = { singleFilters } />
1044+ </ TestWrapper >
1045+ )
1046+
1047+ await waitFor ( ( ) => {
1048+ expect ( mockedAttacksApi . listAttacks ) . toHaveBeenCalled ( )
1049+ } )
1050+
1051+ const singleCallArgs = mockedAttacksApi . listAttacks . mock . calls [ 0 ] [ 0 ]
1052+ expect ( singleCallArgs ) . toEqual ( expect . objectContaining ( { converter_types : [ 'Base64Converter' ] } ) )
1053+ expect ( singleCallArgs ) . not . toHaveProperty ( 'converter_types_match' )
1054+
1055+ unmount ( )
1056+ jest . clearAllMocks ( )
1057+ mockedAttacksApi . listAttacks . mockResolvedValue ( {
1058+ items : [ ] ,
1059+ pagination : { limit : 25 , has_more : false } ,
1060+ } )
1061+ mockedAttacksApi . getAttackOptions . mockResolvedValue ( { attack_types : [ ] } )
1062+ mockedAttacksApi . getConverterOptions . mockResolvedValue ( { converter_types : [ ] } )
1063+ mockedLabelsApi . getLabels . mockResolvedValue ( { source : 'attacks' , labels : { } } )
1064+
1065+ // Case 2: two converters → converter_types_match IS sent
1066+ const multiFilters = {
1067+ ...DEFAULT_HISTORY_FILTERS ,
1068+ converter : [ 'Base64Converter' , 'ROT13Converter' ] ,
1069+ converterMatchMode : 'all' as const ,
1070+ }
1071+
1072+ render (
1073+ < TestWrapper >
1074+ < AttackHistory { ...defaultProps } filters = { multiFilters } />
1075+ </ TestWrapper >
1076+ )
1077+
1078+ await waitFor ( ( ) => {
1079+ expect ( mockedAttacksApi . listAttacks ) . toHaveBeenCalled ( )
1080+ } )
1081+
1082+ expect ( mockedAttacksApi . listAttacks ) . toHaveBeenCalledWith (
1083+ expect . objectContaining ( {
1084+ converter_types : [ 'Base64Converter' , 'ROT13Converter' ] ,
1085+ converter_types_match : 'all' ,
1086+ } )
1087+ )
1088+ } )
9641089} )
0 commit comments