github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/parameters/transferURLs_test.go (about)

     1  /*
     2   * Copyright (c) 2018, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package parameters
    21  
    22  import (
    23  	"encoding/base64"
    24  	"testing"
    25  )
    26  
    27  func TestTransferURLs(t *testing.T) {
    28  
    29  	decodedA := "a.example.com"
    30  	encodedA := base64.StdEncoding.EncodeToString([]byte(decodedA))
    31  	encodedB := base64.StdEncoding.EncodeToString([]byte("b.example.com"))
    32  	encodedC := base64.StdEncoding.EncodeToString([]byte("c.example.com"))
    33  
    34  	testCases := []struct {
    35  		description                string
    36  		transferURLs               TransferURLs
    37  		attempts                   int
    38  		expectedValid              bool
    39  		expectedCanonicalURL       string
    40  		expectedDistinctSelections int
    41  	}{
    42  		{
    43  			"missing OnlyAfterAttempts = 0",
    44  			TransferURLs{
    45  				{
    46  					URL:               encodedA,
    47  					OnlyAfterAttempts: 1,
    48  				},
    49  			},
    50  			1,
    51  			false,
    52  			decodedA,
    53  			0,
    54  		},
    55  		{
    56  			"single URL, multiple attempts",
    57  			TransferURLs{
    58  				{
    59  					URL:               encodedA,
    60  					OnlyAfterAttempts: 0,
    61  				},
    62  			},
    63  			2,
    64  			true,
    65  			decodedA,
    66  			1,
    67  		},
    68  		{
    69  			"multiple URLs, single attempt",
    70  			TransferURLs{
    71  				{
    72  					URL:               encodedA,
    73  					OnlyAfterAttempts: 0,
    74  				},
    75  				{
    76  					URL:               encodedB,
    77  					OnlyAfterAttempts: 1,
    78  				},
    79  				{
    80  					URL:               encodedC,
    81  					OnlyAfterAttempts: 1,
    82  				},
    83  			},
    84  			1,
    85  			true,
    86  			decodedA,
    87  			1,
    88  		},
    89  		{
    90  			"multiple URLs, multiple attempts",
    91  			TransferURLs{
    92  				{
    93  					URL:               encodedA,
    94  					OnlyAfterAttempts: 0,
    95  				},
    96  				{
    97  					URL:               encodedB,
    98  					OnlyAfterAttempts: 1,
    99  				},
   100  				{
   101  					URL:               encodedC,
   102  					OnlyAfterAttempts: 1,
   103  				},
   104  			},
   105  			2,
   106  			true,
   107  			decodedA,
   108  			3,
   109  		},
   110  		{
   111  			"multiple URLs, multiple attempts",
   112  			TransferURLs{
   113  				{
   114  					URL:               encodedA,
   115  					OnlyAfterAttempts: 0,
   116  				},
   117  				{
   118  					URL:               encodedB,
   119  					OnlyAfterAttempts: 1,
   120  				},
   121  				{
   122  					URL:               encodedC,
   123  					OnlyAfterAttempts: 3,
   124  				},
   125  			},
   126  			4,
   127  			true,
   128  			decodedA,
   129  			3,
   130  		},
   131  	}
   132  
   133  	for _, testCase := range testCases {
   134  		t.Run(testCase.description, func(t *testing.T) {
   135  
   136  			err := testCase.transferURLs.DecodeAndValidate()
   137  
   138  			if testCase.expectedValid {
   139  				if err != nil {
   140  					t.Fatalf("unexpected validation error: %s", err)
   141  				}
   142  			} else {
   143  				if err == nil {
   144  					t.Fatalf("expected validation error")
   145  				}
   146  				return
   147  			}
   148  
   149  			// Track distinct selections for each attempt; the
   150  			// expected number of distinct should be for at least
   151  			// one particular attempt.
   152  			attemptDistinctSelections := make(map[int]map[string]int)
   153  			for i := 0; i < testCase.attempts; i++ {
   154  				attemptDistinctSelections[i] = make(map[string]int)
   155  			}
   156  
   157  			// Perform enough runs to account for random selection.
   158  			runs := 1000
   159  
   160  			attempt := 0
   161  			for i := 0; i < runs; i++ {
   162  				canonicalURL := testCase.transferURLs.CanonicalURL()
   163  				if canonicalURL != testCase.expectedCanonicalURL {
   164  					t.Fatalf("unexpected canonical URL: %s", canonicalURL)
   165  				}
   166  				transferUrl := testCase.transferURLs.Select(attempt)
   167  				if transferUrl.SkipVerify {
   168  					t.Fatalf("unexpected skipVerify")
   169  				}
   170  				attemptDistinctSelections[attempt][transferUrl.URL] += 1
   171  				attempt = (attempt + 1) % testCase.attempts
   172  			}
   173  
   174  			maxDistinctSelections := 0
   175  			for _, m := range attemptDistinctSelections {
   176  				if len(m) > maxDistinctSelections {
   177  					maxDistinctSelections = len(m)
   178  				}
   179  			}
   180  
   181  			if maxDistinctSelections != testCase.expectedDistinctSelections {
   182  				t.Fatalf("got %d distinct selections, expected %d",
   183  					maxDistinctSelections,
   184  					testCase.expectedDistinctSelections)
   185  			}
   186  		})
   187  	}
   188  
   189  }