mirror of
https://github.com/qdm12/gluetun.git
synced 2026-08-08 13:23:07 +02:00
fix(vpnsecure): fix updater code with new URL
This commit is contained in:
+8083
-7337
File diff suppressed because one or more lines are too long
@@ -5,7 +5,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
"github.com/qdm12/gluetun/internal/provider/common"
|
||||||
@@ -16,7 +19,7 @@ import (
|
|||||||
func fetchServers(ctx context.Context, client *http.Client,
|
func fetchServers(ctx context.Context, client *http.Client,
|
||||||
warner common.Warner,
|
warner common.Warner,
|
||||||
) (servers []models.Server, err error) {
|
) (servers []models.Server, err error) {
|
||||||
const url = "https://www.vpnsecure.me/vpn-locations/"
|
const url = "https://www.vpnsecure.me/locations/"
|
||||||
rootNode, err := htmlutils.Fetch(ctx, client, url)
|
rootNode, err := htmlutils.Fetch(ctx, client, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fetching HTML code: %w", err)
|
return nil, fmt.Errorf("fetching HTML code: %w", err)
|
||||||
@@ -39,14 +42,14 @@ func parseHTML(rootNode *html.Node) (servers []models.Server,
|
|||||||
warnings []string, err error,
|
warnings []string, err error,
|
||||||
) {
|
) {
|
||||||
// Find div container for all servers, searching with BFS.
|
// Find div container for all servers, searching with BFS.
|
||||||
serversDiv := findServersDiv(rootNode)
|
serversDiv := htmlutils.BFS(rootNode, htmlutils.MatchID("servers"))
|
||||||
if serversDiv == nil {
|
if serversDiv == nil {
|
||||||
return nil, nil, htmlutils.WrapError(errors.New("HTML servers container div not found"), rootNode)
|
return nil, nil, htmlutils.WrapError(errors.New("HTML servers container div not found"), rootNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
for countryNode := serversDiv.FirstChild; countryNode != nil; countryNode = countryNode.NextSibling {
|
for countryNode := serversDiv.FirstChild; countryNode != nil; countryNode = countryNode.NextSibling {
|
||||||
if countryNode.Data != divString {
|
if countryNode.Data != divString ||
|
||||||
// empty line(s) and tab(s)
|
!htmlutils.HasClassStrings(countryNode, "box", "dark-gray") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,26 +59,20 @@ func parseHTML(rootNode *html.Node) (servers []models.Server,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
grid := htmlutils.BFS(countryNode, matchGridDiv)
|
// Find all server tables within this country container
|
||||||
if grid == nil {
|
for serverNode := countryNode.FirstChild; serverNode != nil; serverNode = serverNode.NextSibling {
|
||||||
warnings = append(warnings, htmlutils.WrapWarning("grid div not found", countryNode))
|
if serverNode.Data != divString {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !htmlutils.HasClassStrings(serverNode, "box", "white") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
gridItems := htmlutils.DirectChildren(grid, matchGridItem)
|
server, warning := parseServerNode(serverNode, country)
|
||||||
if len(gridItems) == 0 {
|
|
||||||
warnings = append(warnings, htmlutils.WrapWarning("no grid item found", grid))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, gridItem := range gridItems {
|
|
||||||
server, warning := parseHTMLGridItem(gridItem)
|
|
||||||
if warning != "" {
|
if warning != "" {
|
||||||
warnings = append(warnings, warning)
|
warnings = append(warnings, warning)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
server.Country = country
|
|
||||||
servers = append(servers, server)
|
servers = append(servers, server)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,168 +80,231 @@ func parseHTML(rootNode *html.Node) (servers []models.Server,
|
|||||||
return servers, warnings, nil
|
return servers, warnings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHTMLGridItem(gridItem *html.Node) (
|
func parseServerNode(node *html.Node, country string) (
|
||||||
server models.Server, warning string,
|
server models.Server, warning string,
|
||||||
) {
|
) {
|
||||||
gridItemDT := htmlutils.DirectChild(gridItem, matchDT)
|
// Find the table within this server box
|
||||||
if gridItemDT == nil {
|
tableNode := htmlutils.DirectChild(node, func(n *html.Node) bool {
|
||||||
return server, htmlutils.WrapWarning("grid item <dt> not found", gridItem)
|
return n != nil && n.Data == "table"
|
||||||
|
})
|
||||||
|
if tableNode == nil {
|
||||||
|
return server, htmlutils.WrapWarning("server table not found", node)
|
||||||
}
|
}
|
||||||
|
|
||||||
host := findHost(gridItemDT)
|
// Check status from green-circle in thead
|
||||||
host = naToEmpty(host)
|
isUp := hasGreenCircle(tableNode)
|
||||||
if host == "" {
|
if !isUp {
|
||||||
return server, htmlutils.WrapWarning("host not found", gridItemDT)
|
warning := "skipping server which is not up"
|
||||||
|
return server, htmlutils.WrapWarning(warning, tableNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
status := findStatus(gridItemDT)
|
// Extract city from thead
|
||||||
if !strings.EqualFold(status, "up") {
|
city := findCity(tableNode)
|
||||||
warning := fmt.Sprintf("skipping server with host %s which has status %q", host, status)
|
|
||||||
warning = htmlutils.WrapWarning(warning, gridItemDT)
|
|
||||||
return server, warning
|
|
||||||
}
|
|
||||||
|
|
||||||
gridItemDD := htmlutils.DirectChild(gridItem, matchDD)
|
|
||||||
if gridItemDD == nil {
|
|
||||||
return server, htmlutils.WrapWarning("grid item dd not found", gridItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
region := findSpanStrong(gridItemDD, "Region:")
|
|
||||||
if region == "" {
|
|
||||||
warning := fmt.Sprintf("region for host %s not found", host)
|
|
||||||
return server, htmlutils.WrapWarning(warning, gridItemDD)
|
|
||||||
}
|
|
||||||
region = naToEmpty(region)
|
|
||||||
|
|
||||||
city := findSpanStrong(gridItemDD, "City:")
|
|
||||||
if city == "" {
|
if city == "" {
|
||||||
warning := fmt.Sprintf("region for host %s not found", host)
|
return server, htmlutils.WrapWarning("city not found", tableNode)
|
||||||
return server, htmlutils.WrapWarning(warning, gridItemDD)
|
|
||||||
}
|
}
|
||||||
city = naToEmpty(city)
|
|
||||||
|
|
||||||
premiumString := findSpanStrong(gridItemDD, "Premium:")
|
// Extract server identifier (e.g., "AU #01") from thead
|
||||||
premiumString = naToEmpty(premiumString)
|
serverID := findServerID(tableNode)
|
||||||
if premiumString == "" {
|
if serverID == "" {
|
||||||
warning := fmt.Sprintf("premium for host %s not found", host)
|
return server, htmlutils.WrapWarning("server ID not found", tableNode)
|
||||||
return server, htmlutils.WrapWarning(warning, gridItemDD)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hostname, err := buildHostname(serverID)
|
||||||
|
if err != nil {
|
||||||
|
return server, htmlutils.WrapWarning(fmt.Sprintf("invalid server ID %q: %v", serverID, err), tableNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Dedicated IP feature (maps to Premium)
|
||||||
|
premium := hasFeature(tableNode, "Dedicated IP")
|
||||||
|
|
||||||
return models.Server{
|
return models.Server{
|
||||||
Region: region,
|
Country: country,
|
||||||
City: city,
|
City: city,
|
||||||
Hostname: host + ".isponeder.com",
|
Hostname: hostname,
|
||||||
Premium: strings.EqualFold(premiumString, "yes"),
|
Premium: premium,
|
||||||
}, ""
|
}, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func naToEmpty(current string) (output string) {
|
var serverIDPattern = regexp.MustCompile(`^([A-Z]{2})\s*#\s*(\d+)$`)
|
||||||
if current == "N / A" {
|
|
||||||
return ""
|
func buildHostname(serverID string) (string, error) {
|
||||||
|
const expectedSubmatches = 3
|
||||||
|
matches := serverIDPattern.FindStringSubmatch(serverID)
|
||||||
|
if len(matches) != expectedSubmatches {
|
||||||
|
return "", errors.New("unexpected format")
|
||||||
}
|
}
|
||||||
return current
|
countryCode := strings.ToLower(matches[1])
|
||||||
|
serverNum := strings.TrimLeft(matches[2], "0")
|
||||||
|
return fmt.Sprintf("%s%s.isponeder.com", countryCode, serverNum), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findCountry(countryNode *html.Node) (country string) {
|
func findCountry(countryNode *html.Node) (country string) {
|
||||||
for node := countryNode.FirstChild; node != nil; node = node.NextSibling {
|
h3Node := htmlutils.DirectChild(countryNode, func(n *html.Node) bool {
|
||||||
if node.Data != "a" {
|
return n != nil && n.Data == "h3"
|
||||||
continue
|
})
|
||||||
}
|
if h3Node == nil {
|
||||||
for subNode := node.FirstChild; subNode != nil; subNode = subNode.NextSibling {
|
return ""
|
||||||
if subNode.Data != "h4" {
|
}
|
||||||
continue
|
|
||||||
}
|
// Extract text content from h3, trimming the flag emoji and whitespace
|
||||||
return subNode.FirstChild.Data
|
var sb strings.Builder
|
||||||
|
for child := h3Node.FirstChild; child != nil; child = child.NextSibling {
|
||||||
|
if child.Type == html.TextNode {
|
||||||
|
sb.WriteString(child.Data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
country = strings.TrimSpace(sb.String())
|
||||||
|
// Strip leading emoji (flag) - flags are typically composed emoji characters
|
||||||
|
for len(country) > 0 {
|
||||||
|
r, size := utf8.DecodeRuneInString(country)
|
||||||
|
if !isEmojiRune(r) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
country = country[size:]
|
||||||
|
}
|
||||||
|
country = strings.TrimSpace(country)
|
||||||
|
return country
|
||||||
|
}
|
||||||
|
|
||||||
|
func findCity(tableNode *html.Node) (city string) {
|
||||||
|
const minThCount = 2
|
||||||
|
theadNode := htmlutils.DirectChild(tableNode, func(n *html.Node) bool {
|
||||||
|
return n != nil && n.Data == "thead"
|
||||||
|
})
|
||||||
|
if theadNode == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the second <th> which contains the city name
|
||||||
|
thNodes := htmlutils.BFS(theadNode, func(n *html.Node) bool {
|
||||||
|
return n != nil && n.Data == "th"
|
||||||
|
})
|
||||||
|
if thNodes == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect th nodes in order
|
||||||
|
var ths []*html.Node
|
||||||
|
for node := thNodes; node != nil; node = node.NextSibling {
|
||||||
|
if node.Data == "th" {
|
||||||
|
ths = append(ths, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ths) < minThCount {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second th contains the city
|
||||||
|
return strings.TrimSpace(getTextContent(ths[1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func findServerID(tableNode *html.Node) (serverID string) {
|
||||||
|
theadNode := htmlutils.DirectChild(tableNode, func(n *html.Node) bool {
|
||||||
|
return n != nil && n.Data == "thead"
|
||||||
|
})
|
||||||
|
if theadNode == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the right-aligned th which contains the server ID
|
||||||
|
for node := htmlutils.BFS(theadNode, func(n *html.Node) bool {
|
||||||
|
return n != nil && n.Data == "th"
|
||||||
|
}); node != nil; node = node.NextSibling {
|
||||||
|
if node.Data == "th" && htmlutils.HasClassStrings(node, "right") {
|
||||||
|
return strings.TrimSpace(getTextContent(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func findServersDiv(rootNode *html.Node) (serversDiv *html.Node) {
|
func hasGreenCircle(tableNode *html.Node) bool {
|
||||||
locationsDiv := htmlutils.BFS(rootNode, matchLocationsListDiv)
|
return htmlutils.BFS(tableNode, func(n *html.Node) bool {
|
||||||
if locationsDiv == nil {
|
return n != nil && n.Data == "div" &&
|
||||||
return nil
|
htmlutils.HasClassStrings(n, "green-circle")
|
||||||
}
|
}) != nil
|
||||||
|
|
||||||
return htmlutils.BFS(locationsDiv, matchServersDiv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func findHost(gridItemDT *html.Node) (host string) {
|
func hasFeature(tableNode *html.Node, featureName string) bool {
|
||||||
hostNode := htmlutils.DirectChild(gridItemDT, matchText)
|
tbodyNode := htmlutils.DirectChild(tableNode, func(n *html.Node) bool {
|
||||||
return strings.TrimSpace(hostNode.Data)
|
return n != nil && n.Data == "tbody"
|
||||||
}
|
})
|
||||||
|
if tbodyNode == nil {
|
||||||
func matchText(node *html.Node) (match bool) {
|
|
||||||
if node.Type != html.TextNode {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
data := strings.TrimSpace(node.Data)
|
|
||||||
return data != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func findStatus(gridItemDT *html.Node) (status string) {
|
for trNode := tbodyNode.FirstChild; trNode != nil; trNode = trNode.NextSibling {
|
||||||
statusNode := htmlutils.DirectChild(gridItemDT, matchStatusSpan)
|
if trNode.Data != "tr" {
|
||||||
return strings.TrimSpace(statusNode.FirstChild.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchServersDiv(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == divString &&
|
|
||||||
htmlutils.HasClassStrings(node, "blk__i")
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchLocationsListDiv(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == divString &&
|
|
||||||
htmlutils.HasClassStrings(node, "locations-list")
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchGridDiv(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == divString &&
|
|
||||||
htmlutils.HasClassStrings(node, "grid--locations")
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchGridItem(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == "dl" &&
|
|
||||||
htmlutils.HasClassStrings(node, "grid__i")
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchDT(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == "dt"
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchDD(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == "dd"
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchStatusSpan(node *html.Node) (match bool) {
|
|
||||||
return node.Data == "span" && htmlutils.HasClassStrings(node, "status")
|
|
||||||
}
|
|
||||||
|
|
||||||
func findSpanStrong(gridItemDD *html.Node, spanData string) (
|
|
||||||
strongValue string,
|
|
||||||
) {
|
|
||||||
spanFound := false
|
|
||||||
for child := gridItemDD.FirstChild; child != nil; child = child.NextSibling {
|
|
||||||
if !htmlutils.MatchData("div")(child) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for subchild := child.FirstChild; subchild != nil; subchild = subchild.NextSibling {
|
// Collect td cells in this row
|
||||||
if htmlutils.MatchData("span")(subchild) && subchild.FirstChild.Data == spanData {
|
var tds []*html.Node
|
||||||
spanFound = true
|
for tdNode := trNode.FirstChild; tdNode != nil; tdNode = tdNode.NextSibling {
|
||||||
break
|
if tdNode.Data == "td" {
|
||||||
|
tds = append(tds, tdNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(tds) == 0 {
|
||||||
if !spanFound {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for subchild := child.FirstChild; subchild != nil; subchild = subchild.NextSibling {
|
feature := strings.TrimSpace(getTextContent(tds[0]))
|
||||||
if htmlutils.MatchData("strong")(subchild) {
|
if feature != featureName {
|
||||||
return subchild.FirstChild.Data
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If only one td with colspan spanning columns, feature exists
|
||||||
|
if len(tds) == 1 && htmlutils.Attribute(tds[0], "colspan") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise check for pink-check icon in subsequent td cells
|
||||||
|
for _, td := range tds[1:] {
|
||||||
|
if htmlutils.BFS(td, func(n *html.Node) bool {
|
||||||
|
return n != nil && n.Data == "img" &&
|
||||||
|
strings.Contains(htmlutils.Attribute(n, "src"), "pink-check")
|
||||||
|
}) != nil {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTextContent(node *html.Node) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||||
|
if child.Type == html.TextNode {
|
||||||
|
sb.WriteString(child.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmojiRune(r rune) bool {
|
||||||
|
for _, rangeStart := range []struct{ lo, hi rune }{
|
||||||
|
// Emoticons
|
||||||
|
{0x1F600, 0x1F64F},
|
||||||
|
// Misc Symbols and Pictographs
|
||||||
|
{0x1F300, 0x1F5FF},
|
||||||
|
// Transport and Map Symbols
|
||||||
|
{0x1F680, 0x1F6FF},
|
||||||
|
// Flags (regional indicator symbols + flag emojis)
|
||||||
|
{0x1F1E0, 0x1F1FF},
|
||||||
|
{0x1F100, 0x1F10A},
|
||||||
|
// Supplemental Symbols
|
||||||
|
{0x1F900, 0x1F9FF},
|
||||||
|
} {
|
||||||
|
if r >= rangeStart.lo && r <= rangeStart.hi {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Catch-all for non-ASCII emoji using Unicode category
|
||||||
|
if r > 0x7E && unicode.In(r, unicode.So) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,42 +33,77 @@ func Test_fetchServers(t *testing.T) {
|
|||||||
servers []models.Server
|
servers []models.Server
|
||||||
errMessage string
|
errMessage string
|
||||||
}{
|
}{
|
||||||
"context canceled": {
|
"context_canceled": {
|
||||||
ctx: canceledCtx,
|
ctx: canceledCtx,
|
||||||
errMessage: `fetching HTML code: Get "https://www.vpnsecure.me/vpn-locations/": context canceled`,
|
errMessage: `fetching HTML code: Get "https://www.vpnsecure.me/locations/": context canceled`,
|
||||||
},
|
},
|
||||||
"success": {
|
"success_with_testdata": {
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
responseStatus: http.StatusOK,
|
responseStatus: http.StatusOK,
|
||||||
responseBody: io.NopCloser(strings.NewReader(`
|
responseBody: io.NopCloser(strings.NewReader(`
|
||||||
<div class="blk blk--white locations-list">
|
<body data-controller="menu" class="locations">
|
||||||
<div class="blk__i">
|
<div id="servers" class="container mt-5 mt-lg-6">
|
||||||
<div>
|
<div class="col-12 box dark-gray d-lg-none">
|
||||||
<a href="https://www.vpnsecure.me/vpn-locations/australia/">
|
<h3>
|
||||||
<h4>Australia</h4>
|
<span class="flag">🇦🇺</span>
|
||||||
</a>
|
Australia
|
||||||
<div class="grid grid--3 grid--locations">
|
</h3>
|
||||||
<dl class="grid__i">
|
<div class="col-12 box white">
|
||||||
<dt>
|
<table>
|
||||||
au1
|
<thead>
|
||||||
<span class="status status--up">up</span>
|
<tr>
|
||||||
</dt>
|
<th><div class="green-circle"></div></th>
|
||||||
<dd>
|
<th>City</th>
|
||||||
<div><span>City:</span> <strong>City</strong></div>
|
<th class="right">AU #01</th>
|
||||||
<div><span>Region:</span> <strong>Region</strong></div>
|
</tr>
|
||||||
<div><span>Premium:</span> <strong>YES</strong></div>
|
</thead>
|
||||||
|
<tbody>
|
||||||
</dd>
|
<tr>
|
||||||
</dl>
|
<td colspan="2">
|
||||||
</div>
|
WireGuard
|
||||||
</div>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
OpenVPN
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
Stealth Mode
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
Streaming
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
Dedicated IP
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
Fast (1Gbps)
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
Adblocker
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
`)),
|
`)),
|
||||||
servers: []models.Server{
|
servers: []models.Server{
|
||||||
{
|
{
|
||||||
Country: "Australia",
|
Country: "Australia",
|
||||||
City: "City",
|
City: "City",
|
||||||
Region: "Region",
|
|
||||||
Hostname: "au1.isponeder.com",
|
Hostname: "au1.isponeder.com",
|
||||||
Premium: true,
|
Premium: true,
|
||||||
},
|
},
|
||||||
@@ -84,7 +119,7 @@ func Test_fetchServers(t *testing.T) {
|
|||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
assert.Equal(t, http.MethodGet, r.Method)
|
assert.Equal(t, http.MethodGet, r.Method)
|
||||||
assert.Equal(t, r.URL.String(), "https://www.vpnsecure.me/vpn-locations/")
|
assert.Equal(t, r.URL.String(), "https://www.vpnsecure.me/locations/")
|
||||||
|
|
||||||
ctxErr := r.Context().Err()
|
ctxErr := r.Context().Err()
|
||||||
if ctxErr != nil {
|
if ctxErr != nil {
|
||||||
@@ -92,7 +127,7 @@ func Test_fetchServers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: testCase.responseStatus,
|
||||||
Status: http.StatusText(testCase.responseStatus),
|
Status: http.StatusText(testCase.responseStatus),
|
||||||
Body: testCase.responseBody,
|
Body: testCase.responseBody,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -122,92 +157,211 @@ func Test_parseHTML(t *testing.T) {
|
|||||||
warnings []string
|
warnings []string
|
||||||
errMessage string
|
errMessage string
|
||||||
}{
|
}{
|
||||||
"empty html": {
|
"empty_html": {
|
||||||
rootNode: parseTestHTML(t, ""),
|
rootNode: parseTestHTML(t, ""),
|
||||||
errMessage: `HTML servers container div not found: in HTML code: <html><head></head><body></body></html>`,
|
errMessage: `HTML servers container div not found: in HTML code: <html><head></head><body></body></html>`,
|
||||||
},
|
},
|
||||||
"test data": {
|
"missing_servers_div": {
|
||||||
rootNode: parseTestDataIndexHTML(t),
|
rootNode: parseTestHTML(t, `<div id="other"></div>`),
|
||||||
warnings: []string{
|
errMessage: "HTML servers container div not found",
|
||||||
"no grid item found: in HTML code: <div class=\"grid grid--3 grid--locations\">\n </div>",
|
},
|
||||||
},
|
"server_without_green_circle_is_skipped": {
|
||||||
//nolint:lll
|
rootNode: parseTestHTML(t, `<div id="servers">
|
||||||
|
<div class="box dark-gray">
|
||||||
|
<h3>Germany</h3>
|
||||||
|
<div class="box white">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><div class="red-circle"></div></th>
|
||||||
|
<th>Berlin</th>
|
||||||
|
<th class="right">DE #01</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`),
|
||||||
|
// servers is nil (all servers skipped)
|
||||||
|
warnings: []string{"skipping server which is not up"},
|
||||||
|
},
|
||||||
|
"server_without_Dedicated_IP_is_not_premium": {
|
||||||
|
rootNode: parseTestHTML(t, `<div id="servers">
|
||||||
|
<div class="box dark-gray">
|
||||||
|
<h3>Germany</h3>
|
||||||
|
<div class="box white">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><div class="green-circle"></div></th>
|
||||||
|
<th>Berlin</th>
|
||||||
|
<th class="right">DE #01</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">Dedicated IP</td>
|
||||||
|
<td class="right"><img src="/purple-cross.svg"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`),
|
||||||
servers: []models.Server{
|
servers: []models.Server{
|
||||||
{Country: "Australia", Region: "Queensland", City: "Brisbane", Hostname: "au1.isponeder.com", Premium: true},
|
{
|
||||||
{Country: "Australia", Region: "New South Wales", City: "Sydney", Hostname: "au2.isponeder.com"},
|
Country: "Germany",
|
||||||
{Country: "Australia", Region: "New South Wales", City: "Sydney", Hostname: "au3.isponeder.com"},
|
City: "Berlin",
|
||||||
{Country: "Australia", Region: "New South Wales", City: "Sydney", Hostname: "au4.isponeder.com", Premium: true},
|
Hostname: "de1.isponeder.com",
|
||||||
{Country: "Austria", Region: "Vienna", City: "Vienna", Hostname: "at1.isponeder.com", Premium: true},
|
Premium: false,
|
||||||
{Country: "Austria", Region: "Vienna", City: "Vienna", Hostname: "at2.isponeder.com"},
|
},
|
||||||
{Country: "Brazil", Region: "Sao Paulo", City: "Sao Paulo", Hostname: "br1.isponeder.com", Premium: true},
|
},
|
||||||
{Country: "Belgium", Region: "Flanders", City: "Zaventem", Hostname: "be1.isponeder.com"},
|
},
|
||||||
{Country: "Belgium", Region: "Brussels Hoofdstedelijk Gewest", City: "Brussel", Hostname: "be2.isponeder.com"},
|
"server_with_Dedicated_IP_is_premium": {
|
||||||
{Country: "Canada", Region: "Ontario", City: "Richmond Hill", Hostname: "ca1.isponeder.com"},
|
rootNode: parseTestHTML(t, `<div id="servers">
|
||||||
{Country: "Canada", Region: "Ontario", City: "Richmond Hill", Hostname: "ca2.isponeder.com"},
|
<div class="box dark-gray">
|
||||||
{Country: "Canada", Region: "Quebec", City: "Montréal", Hostname: "ca3.isponeder.com", Premium: true},
|
<h3>Germany</h3>
|
||||||
{Country: "Denmark", Region: "Capital Region", City: "Copenhagen", Hostname: "dk1.isponeder.com", Premium: true},
|
<div class="box white">
|
||||||
{Country: "Denmark", Region: "Capital Region", City: "Copenhagen", Hostname: "dk2.isponeder.com", Premium: true},
|
<table>
|
||||||
{Country: "Denmark", Region: "Capital Region", City: "Ballerup", Hostname: "dk3.isponeder.com"},
|
<thead>
|
||||||
{Country: "France", Region: "Île-de-France", City: "Paris", Hostname: "fr1.isponeder.com"},
|
<tr>
|
||||||
{Country: "France", Region: "Île-de-France", City: "Paris", Hostname: "fr2.isponeder.com"},
|
<th><div class="green-circle"></div></th>
|
||||||
{Country: "France", Region: "Grand Est", City: "Strasbourg", Hostname: "fr3.isponeder.com"},
|
<th>Berlin</th>
|
||||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", Hostname: "de1.isponeder.com"},
|
<th class="right">DE #01</th>
|
||||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", Hostname: "de2.isponeder.com"},
|
</tr>
|
||||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", Hostname: "de3.isponeder.com"},
|
</thead>
|
||||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", Hostname: "de4.isponeder.com"},
|
<tbody>
|
||||||
{Country: "Germany", Region: "Hesse", City: "Limburg an der Lahn", Hostname: "de5.isponeder.com"},
|
<tr>
|
||||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", Hostname: "de6.isponeder.com"},
|
<td colspan="2">Dedicated IP</td>
|
||||||
{Country: "Hungary", Region: "Budapest", City: "Budapest", Hostname: "hu1.isponeder.com", Premium: true},
|
<td class="right"><img src="/pink-check.svg"></td>
|
||||||
{Country: "India", Region: "Karnataka", City: "Doddaballapura", Hostname: "in1.isponeder.com"},
|
</tr>
|
||||||
{Country: "Indonesia", Region: "Special Capital Region of Jakarta", City: "Jakarta", Hostname: "id1.isponeder.com"},
|
</tbody>
|
||||||
{Country: "Ireland", Region: "Dublin City", City: "Dublin", Hostname: "ie1.isponeder.com"},
|
</table>
|
||||||
{Country: "Israel", Region: "Tel Aviv", City: "Tel Aviv", Hostname: "il1.isponeder.com", Premium: true},
|
</div>
|
||||||
{Country: "Italy", Region: "Lombardy", City: "Milan", Hostname: "it1.isponeder.com", Premium: true},
|
</div>
|
||||||
{Country: "Japan", Region: "Tokyo", City: "Tokyo", Hostname: "jp2.isponeder.com", Premium: true},
|
</div>`),
|
||||||
{Country: "Mexico", Region: "México", City: "Ampliación San Mateo (Colonia Solidaridad)", Hostname: "mx1.isponeder.com"},
|
servers: []models.Server{
|
||||||
{Country: "Netherlands", Region: "North Holland", City: "Haarlem", Hostname: "nl1.isponeder.com"},
|
{
|
||||||
{Country: "Netherlands", Region: "South Holland", City: "Naaldwijk", Hostname: "nl2.isponeder.com"},
|
Country: "Germany",
|
||||||
{Country: "New Zealand", Region: "Auckland", City: "Auckland", Hostname: "nz1.isponeder.com"},
|
City: "Berlin",
|
||||||
{Country: "Norway", Region: "Oslo", City: "Oslo", Hostname: "no1.isponeder.com", Premium: true},
|
Hostname: "de1.isponeder.com",
|
||||||
{Country: "Norway", Region: "Stockholm", City: "Stockholm", Hostname: "no2.isponeder.com", Premium: true},
|
Premium: true,
|
||||||
{Country: "Poland", Region: "Mazovia", City: "Warsaw", Hostname: "pl1.isponeder.com", Premium: true},
|
},
|
||||||
{Country: "Romania", Region: "Bucure?ti", City: "Bucharest", Hostname: "ro1.isponeder.com", Premium: true},
|
},
|
||||||
{Country: "Russia", Region: "Moscow", City: "Moscow", Hostname: "ru1.isponeder.com", Premium: true},
|
},
|
||||||
{Country: "Singapore", Region: "Singapore", City: "Singapore", Hostname: "sg1.isponeder.com", Premium: true},
|
"country_with_flag_emoji_prefix_is_stripped": {
|
||||||
{Country: "South Africa", Region: "Western Cape", City: "Cape Town", Hostname: "za1.isponeder.com", Premium: true},
|
rootNode: parseTestHTML(t, `<div id="servers">
|
||||||
{Country: "Spain", Region: "Madrid", City: "Madrid", Hostname: "es2.isponeder.com"},
|
<div class="box dark-gray">
|
||||||
{Country: "Spain", Region: "Valencia", City: "Valencia", Hostname: "se1.isponeder.com"},
|
<h3><span class="flag">🇩🇪</span> Germany</h3>
|
||||||
{Country: "Sweden", Region: "Stockholm", City: "Stockholm", Hostname: "se2.isponeder.com", Premium: true},
|
<div class="box white">
|
||||||
{Country: "Sweden", Region: "Stockholm", City: "Stockholm", Hostname: "se3.isponeder.com"},
|
<table>
|
||||||
{Country: "Switzerland", Region: "Vaud", City: "Lausanne", Hostname: "ch1.isponeder.com"},
|
<thead>
|
||||||
{Country: "Switzerland", Region: "Geneva", City: "Geneva", Hostname: "ch1.isponeder.com", Premium: true},
|
<tr>
|
||||||
{Country: "Switzerland", Region: "Geneva", City: "Genève", Hostname: "ch2.isponeder.com", Premium: true},
|
<th><div class="green-circle"></div></th>
|
||||||
{Country: "Ukraine", Region: "Poltavs'ka Oblast'", City: "Kremenchuk", Hostname: "ua1.isponeder.com", Premium: true},
|
<th>Berlin</th>
|
||||||
{Country: "United Arab Emirates", Region: "Maharashtra", City: "Mumbai", Hostname: "ae1.isponeder.com", Premium: true},
|
<th class="right">DE #01</th>
|
||||||
{Country: "United Kingdom", Region: "England", City: "London", Hostname: "uk2.isponeder.com"},
|
</tr>
|
||||||
{Country: "United Kingdom", Region: "England", City: "Kent", Hostname: "uk3.isponeder.com"},
|
</thead>
|
||||||
{Country: "United Kingdom", Region: "England", City: "London", Hostname: "uk4.isponeder.com"},
|
<tbody></tbody>
|
||||||
{Country: "United Kingdom", Region: "England", City: "London", Hostname: "uk5.isponeder.com"},
|
</table>
|
||||||
{Country: "United Kingdom", Region: "Brent", City: "Harlesden", Hostname: "uk6.isponeder.com"},
|
</div>
|
||||||
{Country: "United Kingdom", Region: "England", City: "Manchester", Hostname: "uk7.isponeder.com"},
|
</div>
|
||||||
{Country: "United States", Region: "New Jersey", City: "Secaucus", Hostname: "us1.isponeder.com"},
|
</div>`),
|
||||||
{Country: "United States", Region: "New York", City: "New York City", Hostname: "us10.isponeder.com"},
|
servers: []models.Server{
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us11.isponeder.com"},
|
{
|
||||||
{Country: "United States", Region: "Illinois", City: "Chicago", Hostname: "us12.isponeder.com"},
|
Country: "Germany",
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us13.isponeder.com"},
|
City: "Berlin",
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us14.isponeder.com"},
|
Hostname: "de1.isponeder.com",
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us15.isponeder.com"},
|
},
|
||||||
{Country: "United States", Region: "Illinois", City: "Chicago", Hostname: "us16.isponeder.com"},
|
},
|
||||||
{Country: "United States", Region: "New York", City: "New York City", Hostname: "us2.isponeder.com"},
|
},
|
||||||
{Country: "United States", Region: "Oregon", City: "Portland", Hostname: "us3.isponeder.com", Premium: true},
|
"country_name_starting_with_emoji_inline_text": {
|
||||||
{Country: "United States", Region: "Illinois", City: "Chicago", Hostname: "us4.isponeder.com"},
|
rootNode: parseTestHTML(t, `<div id="servers">
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us5.isponeder.com"},
|
<div class="box dark-gray">
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us6.isponeder.com"},
|
<h3>🇯🇵 Japan</h3>
|
||||||
{Country: "United States", Region: "Illinois", City: "Chicago", Hostname: "us7.isponeder.com"},
|
<div class="box white">
|
||||||
{Country: "United States", Region: "Georgia", City: "Atlanta", Hostname: "us8.isponeder.com"},
|
<table>
|
||||||
{Country: "United States", Region: "Georgia", City: "Atlanta", Hostname: "us9.isponeder.com"},
|
<thead>
|
||||||
{Country: "Hong Kong", Region: "Central and Western", City: "Hong Kong", Hostname: "hk1.isponeder.com"},
|
<tr>
|
||||||
{Country: "United States West", Region: "California", City: "Los Angeles", Hostname: "us3.isponeder.com", Premium: true},
|
<th><div class="green-circle"></div></th>
|
||||||
|
<th>Tokyo</th>
|
||||||
|
<th class="right">JP #01</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`),
|
||||||
|
servers: []models.Server{
|
||||||
|
{
|
||||||
|
Country: "Japan",
|
||||||
|
City: "Tokyo",
|
||||||
|
Hostname: "jp1.isponeder.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test_data": {
|
||||||
|
rootNode: parseTestDataIndexHTML(t),
|
||||||
|
servers: []models.Server{
|
||||||
|
{Country: "Australia", City: "Sydney", Hostname: "au1.isponeder.com", Premium: false},
|
||||||
|
{Country: "Brazil", City: "São Paulo", Hostname: "br1.isponeder.com", Premium: false},
|
||||||
|
{Country: "Canada", City: "Montréal", Hostname: "ca.isponeder.com", Premium: true},
|
||||||
|
{Country: "Canada", City: "Montréal", Hostname: "ca1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Canada", City: "Montréal", Hostname: "ca2.isponeder.com", Premium: true},
|
||||||
|
{Country: "Canada", City: "Montréal", Hostname: "ca3.isponeder.com", Premium: true},
|
||||||
|
{Country: "Czech Republic", City: "Prague", Hostname: "cz1.isponeder.com", Premium: true},
|
||||||
|
{Country: "France", City: "Roubaix", Hostname: "fr.isponeder.com", Premium: true},
|
||||||
|
{Country: "France", City: "Roubaix", Hostname: "fr1.isponeder.com", Premium: true},
|
||||||
|
{Country: "France", City: "Roubaix", Hostname: "fr2.isponeder.com", Premium: true},
|
||||||
|
{Country: "France", City: "Strasbourg", Hostname: "fr3.isponeder.com", Premium: true},
|
||||||
|
{Country: "France", City: "Strasbourg", Hostname: "fr4.isponeder.com", Premium: true},
|
||||||
|
{Country: "Germany", City: "Frankfurt", Hostname: "de2.isponeder.com", Premium: true},
|
||||||
|
{Country: "Germany", City: "Limburg", Hostname: "de1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Hong Kong", City: "Hong Kong", Hostname: "hk1.isponeder.com", Premium: false},
|
||||||
|
{Country: "India", City: "Mumbai", Hostname: "in1.isponeder.com", Premium: false},
|
||||||
|
{Country: "Ireland", City: "Dublin", Hostname: "ie1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Ireland", City: "Dublin", Hostname: "ie2.isponeder.com", Premium: true},
|
||||||
|
{Country: "Ireland", City: "Dublin", Hostname: "ie3.isponeder.com", Premium: true},
|
||||||
|
{Country: "Israel", City: "Tel Aviv", Hostname: "il1.isponeder.com", Premium: false},
|
||||||
|
{Country: "Italy", City: "Milan", Hostname: "it1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Italy", City: "Milan", Hostname: "it2.isponeder.com", Premium: true},
|
||||||
|
{Country: "Japan", City: "Tokyo", Hostname: "jp1.isponeder.com", Premium: false},
|
||||||
|
{Country: "Lithuania", City: "Vilnius", Hostname: "lt1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Mexico", City: "Mexico City", Hostname: "mx1.isponeder.com", Premium: false},
|
||||||
|
{Country: "Netherlands", City: "Amsterdam", Hostname: "nl1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Netherlands", City: "Amsterdam", Hostname: "nl2.isponeder.com", Premium: true},
|
||||||
|
{Country: "Poland", City: "Warsaw", Hostname: "pl1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Romania", City: "Bucharest", Hostname: "ro2.isponeder.com", Premium: true},
|
||||||
|
{Country: "Romania", City: "Voluntari", Hostname: "ro1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Russia", City: "Saint Petersburg", Hostname: "ru1.isponeder.com", Premium: false},
|
||||||
|
{Country: "Singapore", City: "Singapore", Hostname: "sg.isponeder.com", Premium: true},
|
||||||
|
{Country: "Singapore", City: "Singapore", Hostname: "sg1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Spain", City: "Madrid", Hostname: "es1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Spain", City: "Madrid", Hostname: "es2.isponeder.com", Premium: true},
|
||||||
|
{Country: "Sweden", City: "Stockholm", Hostname: "se1.isponeder.com", Premium: false},
|
||||||
|
{Country: "Switzerland", City: "Zurich", Hostname: "ch1.isponeder.com", Premium: false},
|
||||||
|
{Country: "Ukraine", City: "Kyiv", Hostname: "ua1.isponeder.com", Premium: true},
|
||||||
|
{Country: "Ukraine", City: "Kyiv", Hostname: "ua2.isponeder.com", Premium: true},
|
||||||
|
{Country: "United Kingdom", City: "Bexleyheath", Hostname: "gb3.isponeder.com", Premium: false},
|
||||||
|
{Country: "United Kingdom", City: "Erith", Hostname: "gb2.isponeder.com", Premium: false},
|
||||||
|
{Country: "United Kingdom", City: "London", Hostname: "gb1.isponeder.com", Premium: false},
|
||||||
|
{Country: "United States", City: "Missouri", Hostname: "us5.isponeder.com", Premium: false},
|
||||||
|
{Country: "United States", City: "New York", Hostname: "us1.isponeder.com", Premium: false},
|
||||||
|
{Country: "United States", City: "New York", Hostname: "us4.isponeder.com", Premium: false},
|
||||||
|
{Country: "United States", City: "Oregon", Hostname: "us6.isponeder.com", Premium: true},
|
||||||
|
{Country: "United States", City: "Oregon", Hostname: "us7.isponeder.com", Premium: true},
|
||||||
|
{Country: "United States", City: "Oregon", Hostname: "us8.isponeder.com", Premium: true},
|
||||||
|
{Country: "United States", City: "Oregon", Hostname: "us9.isponeder.com", Premium: true},
|
||||||
|
{Country: "United States", City: "Oregon", Hostname: "us10.isponeder.com", Premium: true},
|
||||||
|
{Country: "United States", City: "Texas", Hostname: "us2.isponeder.com", Premium: false},
|
||||||
|
{Country: "United States", City: "Texas", Hostname: "us3.isponeder.com", Premium: false},
|
||||||
|
{Country: "United States", City: "Virginia", Hostname: "us11.isponeder.com", Premium: true},
|
||||||
|
{Country: "United States", City: "Virginia", Hostname: "us12.isponeder.com", Premium: true},
|
||||||
|
{Country: "United States", City: "Virginia", Hostname: "us13.isponeder.com", Premium: true},
|
||||||
|
{Country: "United States", City: "Virginia", Hostname: "us14.isponeder.com", Premium: true},
|
||||||
|
{Country: "United States", City: "Virginia", Hostname: "us15.isponeder.com", Premium: true},
|
||||||
|
{Country: "United States", City: "Virginia", Hostname: "us16.isponeder.com", Premium: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -219,9 +373,18 @@ func Test_parseHTML(t *testing.T) {
|
|||||||
servers, warnings, err := parseHTML(testCase.rootNode)
|
servers, warnings, err := parseHTML(testCase.rootNode)
|
||||||
|
|
||||||
assert.Equal(t, testCase.servers, servers)
|
assert.Equal(t, testCase.servers, servers)
|
||||||
assert.Equal(t, testCase.warnings, warnings)
|
for _, expected := range testCase.warnings {
|
||||||
|
found := false
|
||||||
|
for _, actual := range warnings {
|
||||||
|
if strings.Contains(actual, expected) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, found, "warning %q not found in %v", expected, warnings)
|
||||||
|
}
|
||||||
if testCase.errMessage != "" {
|
if testCase.errMessage != "" {
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
assert.ErrorContains(t, err, testCase.errMessage)
|
||||||
} else {
|
} else {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user