Files
gluetun/internal/provider/expressvpn/updater/website_test.go
T
Quentin McGaw c5b9ee89c6 expressvpn wip
2026-08-06 03:22:06 +00:00

207 lines
5.6 KiB
Go

package updater
import (
"os"
"strings"
"testing"
"github.com/qdm12/gluetun/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/html"
)
func Test_parseServerListTable(t *testing.T) {
t.Parallel()
htmlContent, err := os.ReadFile("testdata/index.html")
require.NoError(t, err)
rootNode, err := html.Parse(strings.NewReader(string(htmlContent)))
require.NoError(t, err)
servers, warnings := parseServerListTable(rootNode)
require.NoError(t, err)
assert.Empty(t, warnings)
// The ExpressVPN server list should have hundreds of servers
assert.Greater(t, len(servers), 200,
"expected at least 200 servers from the ExpressVPN server list, got %d", len(servers))
// Check that all servers have required fields
for _, server := range servers {
assert.NotEmpty(t, server.Country, "server country should not be empty")
assert.True(t, server.TCP, "TCP should be enabled")
assert.True(t, server.UDP, "UDP should be enabled")
}
// Check some specific servers are found
foundServers := make(map[string]models.Server)
for _, server := range servers {
key := server.Country
if server.City != "" {
key += " - " + server.City
}
foundServers[key] = server
}
// Verify countries without cities
assert.NotEmpty(t, foundServers["Albania"])
assert.NotEmpty(t, foundServers["Singapore"])
}
func Test_parseLocation(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
input string
country string
city string
number uint16
}{
"country only": {
input: "Argentina",
country: "Argentina",
},
"country with city": {
input: "Australia - Adelaide",
country: "Australia",
city: "Adelaide",
},
"country with numbered location": {
input: "Canada - Toronto - 2",
country: "Canada",
city: "Toronto",
number: 2,
},
"virtual location": {
input: "India (via Singapore)",
country: "India (via Singapore)",
},
"country with multiple dashes in city": {
input: "USA - Los Angeles - 1",
country: "USA",
city: "Los Angeles",
number: 1,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
country, city, number, err := parseLocation(testCase.input)
assert.Equal(t, testCase.country, country)
assert.Equal(t, testCase.city, city)
assert.Equal(t, testCase.number, number)
assert.NoError(t, err)
})
}
}
func Test_generateCandidateHostnames(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
input models.Server
mustContain []string
mustNotContain []string
expectedCount int // exact count expected (0 if flexible)
}{
"simple country": {
input: models.Server{Country: "Argentina"},
mustContain: []string{
"argentina-ca-version-2.expressnetw.com",
"argentina1-ca-version-2.expressnetw.com",
"argentina-1-ca-version-2.expressnetw.com",
},
},
"country with city": {
input: models.Server{Country: "USA", City: "New York"},
mustContain: []string{
"usa-newyork-ca-version-2.expressnetw.com",
"us-newyork-ca-version-2.expressnetw.com", // alias
"usa-newyork-1-ca-version-2.expressnetw.com",
"usa-newyork1-ca-version-2.expressnetw.com",
},
},
"North Macedonia abbreviation": {
input: models.Server{Country: "North Macedonia"},
mustContain: []string{
"macedonia-ca-version-2.expressnetw.com",
},
expectedCount: 1, // single hardcoded hostname
},
"India via Singapore": {
input: models.Server{Country: "India (via Singapore)"},
mustContain: []string{
"india-sg-ca-version-2.expressnetw.com",
},
expectedCount: 1, // single hardcoded hostname
},
"India via UK": {
input: models.Server{Country: "India (via UK)"},
mustContain: []string{
"india-uk-ca-version-2.expressnetw.com",
},
expectedCount: 1, // single hardcoded hostname
},
"France Paris": {
input: models.Server{Country: "France", City: "Paris"},
mustContain: []string{
"france-paris-ca-version-2.expressnetw.com",
"france-paris-1-ca-version-2.expressnetw.com",
"france-paris-2-ca-version-2.expressnetw.com",
"france-paris1-ca-version-2.expressnetw.com",
"france-paris2-ca-version-2.expressnetw.com",
},
},
"UK with alias": {
input: models.Server{Country: "UK", City: "London"},
mustContain: []string{
"uk-london-ca-version-2.expressnetw.com",
"gb-london-ca-version-2.expressnetw.com", // alias
},
},
"Germany": {
input: models.Server{Country: "Germany"},
mustContain: []string{
"germany-ca-version-2.expressnetw.com",
"de-ca-version-2.expressnetw.com", // alias
},
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
candidates, err := generateCandidateHostnames(testCase.input)
assert.NoError(t, err)
// Check exact count if specified
if testCase.expectedCount > 0 {
assert.Len(t, candidates, testCase.expectedCount,
"expected exactly %d candidates", testCase.expectedCount)
}
// Check required candidates exist
for _, expected := range testCase.mustContain {
assert.Contains(t, candidates, expected,
"expected candidate %q in candidates", expected)
}
// Check excluded candidates don't exist
for _, excluded := range testCase.mustNotContain {
assert.NotContains(t, candidates, excluded,
"did not expect candidate %q in candidates", excluded)
}
// All candidates should end with the expected suffix
for _, candidate := range candidates {
assert.True(t, strings.HasSuffix(candidate, hostnameSuffix),
"candidate %q should end with %q", candidate, hostnameSuffix)
}
})
}
}