package updater import ( "context" "errors" "io" "net/http" "strings" "testing" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type httpExchange struct { requestURL string responseStatus int responseBody io.ReadCloser } func Test_addServersFromAPI(t *testing.T) { t.Parallel() testCases := map[string]struct { hts hostToServer exchanges []httpExchange expected hostToServer err error }{ "fetch API error": { exchanges: []httpExchange{{ requestURL: "https://api.surfshark.com/v4/server/clusters/generic", responseStatus: http.StatusNoContent, }}, err: errors.New("HTTP status code not OK: 204 No Content"), }, "success": { hts: hostToServer{ "existinghost": models.Server{Hostname: "existinghost"}, }, exchanges: []httpExchange{{ requestURL: "https://api.surfshark.com/v4/server/clusters/generic", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[ {"connectionName":"host1","region":"region1","country":"country1","location":"location1"}, {"connectionName":"host1","region":"region1","country":"country1","location":"location1","pubkey":"pubKeyValue"}, {"connectionName":"host2","region":"region2","country":"country1","location":"location2"} ]`)), }, { requestURL: "https://api.surfshark.com/v4/server/clusters/double", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }, { requestURL: "https://api.surfshark.com/v4/server/clusters/static", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }, { requestURL: "https://api.surfshark.com/v4/server/clusters/obfuscated", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }}, expected: map[string]models.Server{ "existinghost": {Hostname: "existinghost"}, "host1": { VPN: vpn.Both, Region: "region1", Country: "country1", City: "location1", Hostname: "host1", TCP: true, UDP: true, WgPubKey: "pubKeyValue", }, "host2": { VPN: vpn.OpenVPN, Region: "region2", Country: "country1", City: "location2", Hostname: "host2", TCP: true, UDP: true, }, }, }, } for name, testCase := range testCases { testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() ctx := context.Background() currentExchangeIndex := 0 client := &http.Client{ Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { assert.Equal(t, http.MethodGet, r.Method) exchange := testCase.exchanges[currentExchangeIndex] currentExchangeIndex++ assert.Equal(t, exchange.requestURL, r.URL.String()) return &http.Response{ StatusCode: exchange.responseStatus, Status: http.StatusText(exchange.responseStatus), Body: exchange.responseBody, }, nil }), } err := addServersFromAPI(ctx, client, testCase.hts) assert.Equal(t, testCase.expected, testCase.hts) if testCase.err != nil { require.Error(t, err) assert.Equal(t, testCase.err.Error(), err.Error()) } else { assert.NoError(t, err) } }) } } func Test_fetchAPI(t *testing.T) { t.Parallel() testCases := map[string]struct { exchanges []httpExchange data []serverData err error }{ "http response status not ok": { exchanges: []httpExchange{{ requestURL: "https://api.surfshark.com/v4/server/clusters/generic", responseStatus: http.StatusNoContent, }}, err: errors.New("HTTP status code not OK: 204 No Content"), }, "nil body": { exchanges: []httpExchange{{ requestURL: "https://api.surfshark.com/v4/server/clusters/generic", responseStatus: http.StatusOK, }}, err: errors.New("decoding response body: EOF"), }, "no server": { exchanges: []httpExchange{{ requestURL: "https://api.surfshark.com/v4/server/clusters/generic", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }, { requestURL: "https://api.surfshark.com/v4/server/clusters/double", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }, { requestURL: "https://api.surfshark.com/v4/server/clusters/static", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }, { requestURL: "https://api.surfshark.com/v4/server/clusters/obfuscated", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }}, }, "success": { exchanges: []httpExchange{{ requestURL: "https://api.surfshark.com/v4/server/clusters/generic", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[ {"connectionName":"host1","region":"region1","country":"country1","location":"location1"}, {"connectionName":"host2","region":"region2","country":"country1","location":"location2"} ]`)), }, { requestURL: "https://api.surfshark.com/v4/server/clusters/double", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }, { requestURL: "https://api.surfshark.com/v4/server/clusters/static", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }, { requestURL: "https://api.surfshark.com/v4/server/clusters/obfuscated", responseStatus: http.StatusOK, responseBody: io.NopCloser(strings.NewReader(`[]`)), }}, data: []serverData{ { Region: "region1", Country: "country1", Location: "location1", Host: "host1", }, { Region: "region2", Country: "country1", Location: "location2", Host: "host2", }, }, }, } for name, testCase := range testCases { testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() ctx := context.Background() currentExchangeIndex := 0 client := &http.Client{ Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { assert.Equal(t, http.MethodGet, r.Method) exchange := testCase.exchanges[currentExchangeIndex] currentExchangeIndex++ assert.Equal(t, exchange.requestURL, r.URL.String()) return &http.Response{ StatusCode: exchange.responseStatus, Status: http.StatusText(exchange.responseStatus), Body: exchange.responseBody, }, nil }), } data, err := fetchAPI(ctx, client) assert.Equal(t, testCase.data, data) if testCase.err != nil { require.Error(t, err) assert.Equal(t, testCase.err.Error(), err.Error()) } else { assert.NoError(t, err) } }) } }