1// Copyright (C) 2019 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package trust_test
5
6import (
7	"context"
8	"fmt"
9	"net/http"
10	"net/http/httptest"
11	"testing"
12
13	"github.com/stretchr/testify/assert"
14	"github.com/stretchr/testify/require"
15
16	"storj.io/storj/storagenode/trust"
17)
18
19func TestHTTPSourceNew(t *testing.T) {
20	for _, tt := range []struct {
21		name    string
22		httpURL string
23		errs    []string
24	}{
25		{
26			name:    "not a valid URL",
27			httpURL: "://",
28			errs: []string{
29				`HTTP source: "://": not a URL: parse ://: missing protocol scheme`,
30				`HTTP source: "://": not a URL: parse "://": missing protocol scheme`,
31			},
32		},
33		{
34			name:    "not an HTTP or HTTPS URL",
35			httpURL: "file://",
36			errs:    []string{`HTTP source: "file://": scheme is not supported`},
37		},
38		{
39			name:    "missing host",
40			httpURL: "http:///path",
41			errs:    []string{`HTTP source: "http:///path": host is missing`},
42		},
43		{
44			name:    "fragment not allowed",
45			httpURL: "http://localhost/path#OHNO",
46			errs:    []string{`HTTP source: "http://localhost/path#OHNO": fragment is not allowed`},
47		},
48		{
49			name:    "success",
50			httpURL: "http://localhost/path",
51		},
52	} {
53		tt := tt // quiet linting
54		t.Run(tt.name, func(t *testing.T) {
55			_, err := trust.NewHTTPSource(tt.httpURL)
56			if len(tt.errs) > 0 {
57				require.Error(t, err)
58				require.Contains(t, tt.errs, err.Error())
59				return
60			}
61			require.NoError(t, err)
62		})
63	}
64}
65
66func TestHTTPSourceString(t *testing.T) {
67	source, err := trust.NewHTTPSource("http://localhost:1234/path")
68	require.NoError(t, err)
69	require.Equal(t, "http://localhost:1234/path", source.String())
70}
71
72func TestHTTPSourceIsNotStatic(t *testing.T) {
73	source, err := trust.NewHTTPSource("http://localhost/path")
74	require.NoError(t, err)
75	require.False(t, source.Static(), "HTTP source is unexpectedly static")
76}
77
78func TestHTTPSourceFetchEntries(t *testing.T) {
79	url1 := makeSatelliteURL("127.0.0.1")
80	url2 := makeSatelliteURL("domain.test")
81
82	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
83		switch {
84		case r.Method != "GET":
85			http.Error(w, fmt.Sprintf("%s method not allowed", r.Method), http.StatusMethodNotAllowed)
86		case r.URL.Path == "/good":
87			fmt.Fprintf(w, `
88				# Some comment
89				%s
90				%s
91			`, url1.String(), url2.String())
92		case r.URL.Path == "/bad":
93			fmt.Fprintln(w, "BAD")
94		case r.URL.Path == "/ugly":
95			http.Error(w, "OHNO", http.StatusInternalServerError)
96		}
97	}))
98	defer server.Close()
99
100	goodURL := server.URL + "/good"
101	badURL := server.URL + "/bad"
102	uglyURL := server.URL + "/ugly"
103
104	for _, tt := range []struct {
105		name    string
106		httpURL string
107		err     string
108		entries []trust.Entry
109	}{
110		{
111			name:    "well-formed list was fetched",
112			httpURL: goodURL,
113			entries: []trust.Entry{
114				{
115					SatelliteURL:  url1,
116					Authoritative: true,
117				},
118				{
119					SatelliteURL:  url2,
120					Authoritative: false,
121				},
122			},
123		},
124		{
125			name:    "malformed list was fetched",
126			httpURL: badURL,
127			err:     fmt.Sprintf("HTTP source: cannot parse list at %q: invalid satellite URL: must contain an ID", badURL),
128		},
129		{
130			name:    "endpoint returned unsuccessful status code",
131			httpURL: uglyURL,
132			err:     fmt.Sprintf(`HTTP source: %q: unexpected status code 500: "OHNO"`, uglyURL),
133		},
134	} {
135		tt := tt // quiet linting
136		t.Run(tt.name, func(t *testing.T) {
137			source, err := trust.NewHTTPSource(tt.httpURL)
138			require.NoError(t, err)
139			entries, err := source.FetchEntries(context.Background())
140			if tt.err != "" {
141				require.EqualError(t, err, tt.err)
142				return
143			}
144			require.NoError(t, err)
145			assert.Equal(t, tt.entries, entries)
146		})
147	}
148}
149
150func TestURLMatchesHTTPSourceHost(t *testing.T) {
151	for _, tt := range []struct {
152		name       string
153		urlHost    string
154		sourceHost string
155		matches    bool
156	}{
157		{
158			name:       "URL IP and source domain should not match",
159			urlHost:    "1.2.3.4",
160			sourceHost: "domain.test",
161			matches:    false,
162		},
163		{
164			name:       "URL domain and source IP should not match",
165			urlHost:    "domain.test",
166			sourceHost: "1.2.3.4",
167			matches:    false,
168		},
169		{
170			name:       "equal URL and source IP should match",
171			urlHost:    "1.2.3.4",
172			sourceHost: "1.2.3.4",
173			matches:    true,
174		},
175		{
176			name:       "inequal URL and source IP should not match",
177			urlHost:    "1.2.3.4",
178			sourceHost: "4.3.2.1",
179			matches:    false,
180		},
181		{
182			name:       "equal URL and source domains should match",
183			urlHost:    "domain.test",
184			sourceHost: "domain.test",
185			matches:    true,
186		},
187		{
188			name:       "URL domain and source subdomains should not match",
189			urlHost:    "domain.test",
190			sourceHost: "sub.domain.test",
191			matches:    false,
192		},
193		{
194			name:       "URL subdomain and source domain should match",
195			urlHost:    "sub.domain.test",
196			sourceHost: "domain.test",
197			matches:    true,
198		},
199	} {
200		tt := tt // quiet linting
201		t.Run(tt.name, func(t *testing.T) {
202			require.Equal(t, tt.matches, trust.URLMatchesHTTPSourceHost(tt.urlHost, tt.sourceHost))
203		})
204	}
205}
206