github.com/prebid/prebid-server/v2@v2.18.0/privacysandbox/topics_test.go (about)

     1  package privacysandbox
     2  
     3  import (
     4  	"encoding/json"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/prebid/openrtb/v20/openrtb2"
     9  	"github.com/prebid/prebid-server/v2/errortypes"
    10  	"github.com/stretchr/testify/assert"
    11  )
    12  
    13  func TestParseTopicsFromHeader(t *testing.T) {
    14  	type args struct {
    15  		secBrowsingTopics string
    16  	}
    17  	tests := []struct {
    18  		name      string
    19  		args      args
    20  		wantTopic []Topic
    21  		wantError []error
    22  	}{
    23  		{
    24  			name:      "empty header",
    25  			args:      args{secBrowsingTopics: "	 "},
    26  			wantTopic: []Topic{},
    27  			wantError: nil,
    28  		},
    29  		{
    30  			name:      "invalid header value",
    31  			args:      args{secBrowsingTopics: "some-sec-cookie-value"},
    32  			wantTopic: []Topic{},
    33  			wantError: []error{
    34  				&errortypes.DebugWarning{
    35  					Message:     "Invalid field in Sec-Browsing-Topics header: some-sec-cookie-value",
    36  					WarningCode: errortypes.SecBrowsingTopicsWarningCode,
    37  				},
    38  			},
    39  		},
    40  		{
    41  			name:      "header with only finish padding",
    42  			args:      args{secBrowsingTopics: "();p=P0000000000000000000000000000000"},
    43  			wantTopic: []Topic{},
    44  			wantError: nil,
    45  		},
    46  		{
    47  			name: "header with one valid field",
    48  			args: args{secBrowsingTopics: "(1);v=chrome.1:1:2, ();p=P00000000000"},
    49  			wantTopic: []Topic{
    50  				{
    51  					SegTax:   600,
    52  					SegClass: "2",
    53  					SegIDs:   []int{1},
    54  				},
    55  			},
    56  			wantError: nil,
    57  		},
    58  		{
    59  			name: "header without finish padding",
    60  			args: args{secBrowsingTopics: "(1);v=chrome.1:1:2"},
    61  			wantTopic: []Topic{
    62  				{
    63  					SegTax:   600,
    64  					SegClass: "2",
    65  					SegIDs:   []int{1},
    66  				},
    67  			},
    68  			wantError: nil,
    69  		},
    70  		{
    71  			name: "header with more than 10 valid field, should return only 10",
    72  			args: args{secBrowsingTopics: "(1);v=chrome.1:1:2, (2);v=chrome.1:1:2, (3);v=chrome.1:1:2,  (4);v=chrome.1:1:2,  (5);v=chrome.1:1:2,  (6);v=chrome.1:1:2,  (7);v=chrome.1:1:2,  (8);v=chrome.1:1:2,  (9);v=chrome.1:1:2,  (10);v=chrome.1:1:2,  (11);v=chrome.1:1:2,  (12);v=chrome.1:1:2, ();p=P00000000000"},
    73  			wantTopic: []Topic{
    74  				{
    75  					SegTax:   600,
    76  					SegClass: "2",
    77  					SegIDs:   []int{1},
    78  				},
    79  				{
    80  					SegTax:   600,
    81  					SegClass: "2",
    82  					SegIDs:   []int{2},
    83  				},
    84  				{
    85  					SegTax:   600,
    86  					SegClass: "2",
    87  					SegIDs:   []int{3},
    88  				},
    89  				{
    90  					SegTax:   600,
    91  					SegClass: "2",
    92  					SegIDs:   []int{4},
    93  				},
    94  				{
    95  					SegTax:   600,
    96  					SegClass: "2",
    97  					SegIDs:   []int{5},
    98  				},
    99  				{
   100  					SegTax:   600,
   101  					SegClass: "2",
   102  					SegIDs:   []int{6},
   103  				},
   104  				{
   105  					SegTax:   600,
   106  					SegClass: "2",
   107  					SegIDs:   []int{7},
   108  				},
   109  				{
   110  					SegTax:   600,
   111  					SegClass: "2",
   112  					SegIDs:   []int{8},
   113  				},
   114  				{
   115  					SegTax:   600,
   116  					SegClass: "2",
   117  					SegIDs:   []int{9},
   118  				},
   119  				{
   120  					SegTax:   600,
   121  					SegClass: "2",
   122  					SegIDs:   []int{10},
   123  				},
   124  			},
   125  			wantError: []error{
   126  				&errortypes.DebugWarning{
   127  					Message:     "Invalid field in Sec-Browsing-Topics header: (11);v=chrome.1:1:2 discarded due to limit reached.",
   128  					WarningCode: errortypes.SecBrowsingTopicsWarningCode,
   129  				},
   130  				&errortypes.DebugWarning{
   131  					Message:     "Invalid field in Sec-Browsing-Topics header: (12);v=chrome.1:1:2 discarded due to limit reached.",
   132  					WarningCode: errortypes.SecBrowsingTopicsWarningCode,
   133  				},
   134  			},
   135  		},
   136  		{
   137  			name: "header with one valid field having multiple segIDs",
   138  			args: args{secBrowsingTopics: "(1 2);v=chrome.1:1:2, ();p=P00000000000"},
   139  			wantTopic: []Topic{
   140  				{
   141  					SegTax:   600,
   142  					SegClass: "2",
   143  					SegIDs:   []int{1, 2},
   144  				},
   145  			},
   146  			wantError: nil,
   147  		},
   148  		{
   149  			name: "header with two valid fields having different taxonomies",
   150  			args: args{secBrowsingTopics: "(1);v=chrome.1:1:2, (1);v=chrome.1:2:2, ();p=P0000000000"},
   151  			wantTopic: []Topic{
   152  				{
   153  					SegTax:   600,
   154  					SegClass: "2",
   155  					SegIDs:   []int{1},
   156  				},
   157  				{
   158  					SegTax:   601,
   159  					SegClass: "2",
   160  					SegIDs:   []int{1},
   161  				},
   162  			},
   163  			wantError: nil,
   164  		},
   165  		{
   166  			name: "header with one valid field and another invalid field (w/o segIDs), should return only one valid field",
   167  			args: args{secBrowsingTopics: "(1);v=chrome.1:2:3, ();v=chrome.1:2:3, ();p=P0000000000"},
   168  			wantTopic: []Topic{
   169  				{
   170  					SegTax:   601,
   171  					SegClass: "3",
   172  					SegIDs:   []int{1},
   173  				},
   174  			},
   175  			wantError: []error{
   176  				&errortypes.DebugWarning{
   177  					Message:     "Invalid field in Sec-Browsing-Topics header: ();v=chrome.1:2:3",
   178  					WarningCode: errortypes.SecBrowsingTopicsWarningCode,
   179  				},
   180  			},
   181  		},
   182  		{
   183  			name: "header with two valid fields having different model version",
   184  			args: args{secBrowsingTopics: "(1);v=chrome.1:2:3, (2);v=chrome.1:2:3, ();p=P0000000000"},
   185  			wantTopic: []Topic{
   186  				{
   187  					SegTax:   601,
   188  					SegClass: "3",
   189  					SegIDs:   []int{1},
   190  				},
   191  				{
   192  					SegTax:   601,
   193  					SegClass: "3",
   194  					SegIDs:   []int{2},
   195  				},
   196  			},
   197  			wantError: nil,
   198  		},
   199  		{
   200  			name: "header with one valid fields and two invalid fields (one with taxanomy < 0 and another with taxanomy > 10), should return only one valid field",
   201  			args: args{secBrowsingTopics: "(1);v=chrome.1:11:2, (1);v=chrome.1:5:6, (1);v=chrome.1:0:2, ();p=P0000000000"},
   202  			wantTopic: []Topic{
   203  				{
   204  					SegTax:   604,
   205  					SegClass: "6",
   206  					SegIDs:   []int{1},
   207  				},
   208  			},
   209  			wantError: []error{
   210  				&errortypes.DebugWarning{
   211  					Message:     "Invalid field in Sec-Browsing-Topics header: (1);v=chrome.1:11:2",
   212  					WarningCode: errortypes.SecBrowsingTopicsWarningCode,
   213  				},
   214  				&errortypes.DebugWarning{
   215  					Message:     "Invalid field in Sec-Browsing-Topics header: (1);v=chrome.1:0:2",
   216  					WarningCode: errortypes.SecBrowsingTopicsWarningCode,
   217  				},
   218  			},
   219  		},
   220  		{
   221  			name: "header with with valid fields having special characters (whitespaces, etc)",
   222  			args: args{secBrowsingTopics: "(1 2 4		6 7			4567	  ) ; v=chrome.1: 1 : 2, (1);v=chrome.1, ();p=P0000000000"},
   223  			wantTopic: []Topic{
   224  				{
   225  					SegTax:   600,
   226  					SegClass: "2",
   227  					SegIDs:   []int{1, 2, 4, 6, 7, 4567},
   228  				},
   229  			},
   230  			wantError: []error{
   231  				&errortypes.DebugWarning{
   232  					Message:     "Invalid field in Sec-Browsing-Topics header: (1);v=chrome.1",
   233  					WarningCode: errortypes.SecBrowsingTopicsWarningCode,
   234  				},
   235  			},
   236  		},
   237  		{
   238  			name:      "header with one valid field having a negative segId, drop field",
   239  			args:      args{secBrowsingTopics: "(1 -3);v=chrome.1:1:2, ();p=P00000000000"},
   240  			wantTopic: []Topic{},
   241  			wantError: []error{
   242  				&errortypes.DebugWarning{
   243  					Message:     "Invalid field in Sec-Browsing-Topics header: (1 -3);v=chrome.1:1:2",
   244  					WarningCode: errortypes.SecBrowsingTopicsWarningCode,
   245  				},
   246  			},
   247  		},
   248  		{
   249  			name:      "header with one valid field having a segId=0, drop field",
   250  			args:      args{secBrowsingTopics: "(1 0);v=chrome.1:1:2, ();p=P00000000000"},
   251  			wantTopic: []Topic{},
   252  			wantError: []error{
   253  				&errortypes.DebugWarning{
   254  					Message:     "Invalid field in Sec-Browsing-Topics header: (1 0);v=chrome.1:1:2",
   255  					WarningCode: errortypes.SecBrowsingTopicsWarningCode,
   256  				},
   257  			},
   258  		},
   259  		{
   260  			name:      "header with one valid field having a segId value more than MaxInt, drop field",
   261  			args:      args{secBrowsingTopics: "(1 9223372036854775808);v=chrome.1:1:2, ();p=P00000000000"},
   262  			wantTopic: []Topic{},
   263  			wantError: []error{
   264  				&errortypes.DebugWarning{
   265  					Message:     "Invalid field in Sec-Browsing-Topics header: (1 9223372036854775808);v=chrome.1:1:2",
   266  					WarningCode: errortypes.SecBrowsingTopicsWarningCode,
   267  				},
   268  			},
   269  		},
   270  	}
   271  	for _, tt := range tests {
   272  		t.Run(tt.name, func(t *testing.T) {
   273  			gotTopic, gotError := ParseTopicsFromHeader(tt.args.secBrowsingTopics)
   274  			assert.Equal(t, tt.wantTopic, gotTopic)
   275  			assert.Equal(t, tt.wantError, gotError)
   276  		})
   277  	}
   278  }
   279  
   280  func TestUpdateUserDataWithTopics(t *testing.T) {
   281  	type args struct {
   282  		userData     []openrtb2.Data
   283  		headerData   []Topic
   284  		topicsDomain string
   285  	}
   286  	tests := []struct {
   287  		name string
   288  		args args
   289  		want []openrtb2.Data
   290  	}{
   291  		{
   292  			name: "empty topics, empty user data, no change in user data",
   293  			args: args{
   294  				userData:   nil,
   295  				headerData: nil,
   296  			},
   297  			want: nil,
   298  		},
   299  		{
   300  			name: "empty topics, non-empty user data, no change in user data",
   301  			args: args{
   302  				userData: []openrtb2.Data{
   303  					{
   304  						ID:   "1",
   305  						Name: "data1",
   306  						Segment: []openrtb2.Segment{
   307  							{ID: "1"},
   308  							{ID: "2"},
   309  						},
   310  					},
   311  				},
   312  				headerData: nil,
   313  			},
   314  			want: []openrtb2.Data{
   315  				{
   316  					ID:   "1",
   317  					Name: "data1",
   318  					Segment: []openrtb2.Segment{
   319  						{ID: "1"},
   320  						{ID: "2"},
   321  					},
   322  				},
   323  			},
   324  		},
   325  		{
   326  			name: "topicsDomain empty, no change in user data",
   327  			args: args{
   328  				userData: []openrtb2.Data{
   329  					{
   330  						ID:   "1",
   331  						Name: "data1",
   332  						Segment: []openrtb2.Segment{
   333  							{ID: "1"},
   334  							{ID: "2"},
   335  						},
   336  					},
   337  				},
   338  				headerData: []Topic{
   339  					{
   340  						SegTax:   600,
   341  						SegClass: "2",
   342  						SegIDs:   []int{1, 2},
   343  					},
   344  				},
   345  				topicsDomain: "",
   346  			},
   347  			want: []openrtb2.Data{
   348  				{
   349  					ID:   "1",
   350  					Name: "data1",
   351  					Segment: []openrtb2.Segment{
   352  						{ID: "1"},
   353  						{ID: "2"},
   354  					},
   355  				},
   356  			},
   357  		},
   358  		{
   359  			name: "non-empty topics, empty user data, topics from header copied to user data",
   360  			args: args{
   361  				userData: nil,
   362  				headerData: []Topic{
   363  					{
   364  						SegTax:   600,
   365  						SegClass: "2",
   366  						SegIDs:   []int{1, 2},
   367  					},
   368  				},
   369  				topicsDomain: "ads.pubmatic.com",
   370  			},
   371  			want: []openrtb2.Data{
   372  				{
   373  					Name: "ads.pubmatic.com",
   374  					Segment: []openrtb2.Segment{
   375  						{ID: "1"},
   376  						{ID: "2"},
   377  					},
   378  					Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   379  				},
   380  			},
   381  		},
   382  		{
   383  			name: "non-empty topics, non-empty user data, topics from header copied to user data",
   384  			args: args{
   385  				userData: []openrtb2.Data{
   386  					{
   387  						ID:   "1",
   388  						Name: "data1",
   389  						Segment: []openrtb2.Segment{
   390  							{ID: "1"},
   391  							{ID: "2"},
   392  						},
   393  					},
   394  				},
   395  				headerData: []Topic{
   396  					{
   397  						SegTax:   600,
   398  						SegClass: "2",
   399  						SegIDs:   []int{3, 4},
   400  					},
   401  				},
   402  				topicsDomain: "ads.pubmatic.com",
   403  			},
   404  			want: []openrtb2.Data{
   405  				{
   406  					ID:   "1",
   407  					Name: "data1",
   408  					Segment: []openrtb2.Segment{
   409  						{ID: "1"},
   410  						{ID: "2"},
   411  					},
   412  				},
   413  				{
   414  					Name: "ads.pubmatic.com",
   415  					Segment: []openrtb2.Segment{
   416  						{ID: "3"},
   417  						{ID: "4"},
   418  					},
   419  					Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   420  				},
   421  			},
   422  		},
   423  		{
   424  			name: "non-empty topics, user data with invalid data.ext field, topics from header copied to user data",
   425  			args: args{
   426  				userData: []openrtb2.Data{
   427  					{
   428  						ID:   "1",
   429  						Name: "data1",
   430  						Segment: []openrtb2.Segment{
   431  							{ID: "1"},
   432  							{ID: "2"},
   433  						},
   434  						Ext: json.RawMessage(`{`),
   435  					},
   436  				},
   437  				headerData: []Topic{
   438  					{
   439  						SegTax:   600,
   440  						SegClass: "2",
   441  						SegIDs:   []int{3, 4},
   442  					},
   443  				},
   444  				topicsDomain: "ads.pubmatic.com",
   445  			},
   446  			want: []openrtb2.Data{
   447  				{
   448  					ID:   "1",
   449  					Name: "data1",
   450  					Segment: []openrtb2.Segment{
   451  						{ID: "1"},
   452  						{ID: "2"},
   453  					},
   454  					Ext: json.RawMessage(`{`),
   455  				},
   456  				{
   457  					Name: "ads.pubmatic.com",
   458  					Segment: []openrtb2.Segment{
   459  						{ID: "3"},
   460  						{ID: "4"},
   461  					},
   462  					Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   463  				},
   464  			},
   465  		},
   466  		{
   467  			name: "non-empty topics, user data with invalid topic details (invalid segtax and segclass), topics from header copied to user data",
   468  			args: args{
   469  				userData: []openrtb2.Data{
   470  					{
   471  						ID:   "1",
   472  						Name: "chrome.com",
   473  						Segment: []openrtb2.Segment{
   474  							{ID: "1"},
   475  							{ID: "2"},
   476  						},
   477  						Ext: json.RawMessage(`{"segtax":0,"segclass":""}`),
   478  					},
   479  				},
   480  				headerData: []Topic{
   481  					{
   482  						SegTax:   600,
   483  						SegClass: "2",
   484  						SegIDs:   []int{3, 4},
   485  					},
   486  				},
   487  				topicsDomain: "ads.pubmatic.com",
   488  			},
   489  			want: []openrtb2.Data{
   490  				{
   491  					ID:   "1",
   492  					Name: "chrome.com",
   493  					Segment: []openrtb2.Segment{
   494  						{ID: "1"},
   495  						{ID: "2"},
   496  					},
   497  					Ext: json.RawMessage(`{"segtax":0,"segclass":""}`),
   498  				},
   499  				{
   500  					Name: "ads.pubmatic.com",
   501  					Segment: []openrtb2.Segment{
   502  						{ID: "3"},
   503  						{ID: "4"},
   504  					},
   505  					Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   506  				},
   507  			},
   508  		},
   509  		{
   510  			name: "non-empty topics, user data with non matching topic details (different topicdomains, segtax and segclass), topics from header copied to user data",
   511  			args: args{
   512  				userData: []openrtb2.Data{
   513  					{
   514  						ID:   "1",
   515  						Name: "chrome.com",
   516  						Segment: []openrtb2.Segment{
   517  							{ID: "1"},
   518  							{ID: "2"},
   519  						},
   520  						Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   521  					},
   522  					{
   523  						ID:   "2",
   524  						Name: "ads.pubmatic.com",
   525  						Segment: []openrtb2.Segment{
   526  							{ID: "5"},
   527  							{ID: "6"},
   528  						},
   529  						Ext: json.RawMessage(`{"segtax":601,"segclass":"3"}`),
   530  					},
   531  					{
   532  						ID:   "3",
   533  						Name: "ads.pubmatic.com",
   534  						Segment: []openrtb2.Segment{
   535  							{ID: "7"},
   536  							{ID: "8"},
   537  						},
   538  						Ext: json.RawMessage(`{"segtax":602,"segclass":"4"}`),
   539  					},
   540  				},
   541  				headerData: []Topic{
   542  					{
   543  						SegTax:   600,
   544  						SegClass: "2",
   545  						SegIDs:   []int{3, 4},
   546  					},
   547  					{
   548  						SegTax:   602,
   549  						SegClass: "2",
   550  						SegIDs:   []int{3, 4},
   551  					},
   552  				},
   553  				topicsDomain: "ads.pubmatic.com",
   554  			},
   555  			want: []openrtb2.Data{
   556  				{
   557  					ID:   "1",
   558  					Name: "chrome.com",
   559  					Segment: []openrtb2.Segment{
   560  						{ID: "1"},
   561  						{ID: "2"},
   562  					},
   563  					Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   564  				},
   565  				{
   566  					ID:   "2",
   567  					Name: "ads.pubmatic.com",
   568  					Segment: []openrtb2.Segment{
   569  						{ID: "5"},
   570  						{ID: "6"},
   571  					},
   572  					Ext: json.RawMessage(`{"segtax":601,"segclass":"3"}`),
   573  				},
   574  				{
   575  					ID:   "3",
   576  					Name: "ads.pubmatic.com",
   577  					Segment: []openrtb2.Segment{
   578  						{ID: "7"},
   579  						{ID: "8"},
   580  					},
   581  					Ext: json.RawMessage(`{"segtax":602,"segclass":"4"}`),
   582  				},
   583  				{
   584  					Name: "ads.pubmatic.com",
   585  					Segment: []openrtb2.Segment{
   586  						{ID: "3"},
   587  						{ID: "4"},
   588  					},
   589  					Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   590  				},
   591  				{
   592  					Name: "ads.pubmatic.com",
   593  					Segment: []openrtb2.Segment{
   594  						{ID: "3"},
   595  						{ID: "4"},
   596  					},
   597  					Ext: json.RawMessage(`{"segtax":602,"segclass":"2"}`),
   598  				},
   599  			},
   600  		},
   601  		{
   602  			name: "non-empty topics, user data with same topic details (matching segtax and segclass), topics from header merged with user data (filter unique segIDs)",
   603  			args: args{
   604  				userData: []openrtb2.Data{
   605  					{
   606  						ID:   "1",
   607  						Name: "ads.pubmatic.com",
   608  						Segment: []openrtb2.Segment{
   609  							{ID: "1"},
   610  							{ID: "2"},
   611  							{ID: "3"},
   612  						},
   613  						Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   614  					},
   615  				},
   616  				headerData: []Topic{
   617  					{
   618  						SegTax:   600,
   619  						SegClass: "2",
   620  						SegIDs:   []int{2, 3, 4},
   621  					},
   622  				},
   623  				topicsDomain: "ads.pubmatic.com",
   624  			},
   625  			want: []openrtb2.Data{
   626  				{
   627  					ID:   "1",
   628  					Name: "ads.pubmatic.com",
   629  					Segment: []openrtb2.Segment{
   630  						{ID: "1"},
   631  						{ID: "2"},
   632  						{ID: "3"},
   633  						{ID: "4"},
   634  					},
   635  					Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   636  				},
   637  			},
   638  		},
   639  		{
   640  			name: "non-empty topics, user data with duplicate topic details (matching segtax and segclass and segIDs), topics from header merged with user data (filter unique segIDs), user.data will not be deduped",
   641  			args: args{
   642  				userData: []openrtb2.Data{
   643  					{
   644  						ID:   "1",
   645  						Name: "ads.pubmatic.com",
   646  						Segment: []openrtb2.Segment{
   647  							{ID: "1"},
   648  							{ID: "2"},
   649  							{ID: "3"},
   650  						},
   651  						Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   652  					},
   653  					{
   654  						ID:   "1",
   655  						Name: "ads.pubmatic.com",
   656  						Segment: []openrtb2.Segment{
   657  							{ID: "1"},
   658  							{ID: "2"},
   659  							{ID: "3"},
   660  						},
   661  						Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   662  					},
   663  				},
   664  				headerData: []Topic{
   665  					{
   666  						SegTax:   600,
   667  						SegClass: "2",
   668  						SegIDs:   []int{2, 3, 4},
   669  					},
   670  				},
   671  				topicsDomain: "ads.pubmatic.com",
   672  			},
   673  			want: []openrtb2.Data{
   674  				{
   675  					ID:   "1",
   676  					Name: "ads.pubmatic.com",
   677  					Segment: []openrtb2.Segment{
   678  						{ID: "1"},
   679  						{ID: "2"},
   680  						{ID: "3"},
   681  						{ID: "4"},
   682  					},
   683  					Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   684  				},
   685  				{
   686  					ID:   "1",
   687  					Name: "ads.pubmatic.com",
   688  					Segment: []openrtb2.Segment{
   689  						{ID: "1"},
   690  						{ID: "2"},
   691  						{ID: "3"},
   692  					},
   693  					Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
   694  				},
   695  			},
   696  		},
   697  	}
   698  	for _, tt := range tests {
   699  		t.Run(tt.name, func(t *testing.T) {
   700  			got := UpdateUserDataWithTopics(tt.args.userData, tt.args.headerData, tt.args.topicsDomain)
   701  			sort.Slice(got, func(i, j int) bool {
   702  				if got[i].Name == got[j].Name {
   703  					return string(got[i].Ext) < string(got[j].Ext)
   704  				}
   705  				return got[i].Name < got[j].Name
   706  			})
   707  			sort.Slice(tt.want, func(i, j int) bool {
   708  				if tt.want[i].Name == tt.want[j].Name {
   709  					return string(tt.want[i].Ext) < string(tt.want[j].Ext)
   710  				}
   711  				return tt.want[i].Name < tt.want[j].Name
   712  			})
   713  
   714  			for g := range got {
   715  				sort.Slice(got[g].Segment, func(i, j int) bool {
   716  					return got[g].Segment[i].ID < got[g].Segment[j].ID
   717  				})
   718  			}
   719  			assert.Equal(t, tt.want, got, tt.name)
   720  		})
   721  	}
   722  }