Fix readme typo.
[champa.git] / amp / cache_test.go
blob45950fd25528cf1d1a943726af78f67321cf353b
1 package amp
3 import (
4 "bytes"
5 "net/url"
6 "testing"
8 "golang.org/x/net/idna"
11 func TestDomainPrefixBasic(t *testing.T) {
12 // Tests expecting no error.
13 for _, test := range []struct {
14 domain, expected string
16 {"", ""},
17 {"xn--", ""},
18 {"...", "---"},
20 // Should not apply mappings such as case folding and
21 // normalization.
22 {"b\u00fccher.de", "xn--bcher-de-65a"},
23 {"B\u00fccher.de", "xn--Bcher-de-65a"},
24 {"bu\u0308cher.de", "xn--bucher-de-hkf"},
26 // Check some that differ between IDNA 2003 and IDNA 2008.
27 // https://unicode.org/reports/tr46/#Deviations
28 // https://util.unicode.org/UnicodeJsps/idna.jsp
29 {"faß.de", "xn--fa-de-mqa"},
30 {"βόλοσ.com", "xn---com-4ld8c2a6a8e"},
32 // Lengths of 63 and 64. 64 is too long for a DNS label, but
33 // domainPrefixBasic is not expected to check for that.
34 {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
35 {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
37 // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#basic-algorithm
38 {"example.com", "example-com"},
39 {"foo.example.com", "foo-example-com"},
40 {"foo-example.com", "foo--example-com"},
41 {"xn--57hw060o.com", "xn---com-p33b41770a"},
42 {"\u26a1\U0001f60a.com", "xn---com-p33b41770a"},
43 {"en-us.example.com", "0-en--us-example-com-0"},
44 } {
45 output, err := domainPrefixBasic(test.domain)
46 if err != nil || output != test.expected {
47 t.Errorf("%+q → (%+q, %v), expected (%+q, %v)",
48 test.domain, output, err, test.expected, nil)
52 // Tests expecting an error.
53 for _, domain := range []string{
54 "xn---",
55 } {
56 output, err := domainPrefixBasic(domain)
57 if err == nil || output != "" {
58 t.Errorf("%+q → (%+q, %v), expected (%+q, non-nil)",
59 domain, output, err, "")
64 func TestDomainPrefixFallback(t *testing.T) {
65 for _, test := range []struct {
66 domain, expected string
69 "",
70 "4oymiquy7qobjgx36tejs35zeqt24qpemsnzgtfeswmrw6csxbkq",
73 "example.com",
74 "un42n5xov642kxrxrqiyanhcoupgql5lt4wtbkyt2ijflbwodfdq",
77 // These checked against the output of
78 // https://github.com/ampproject/amp-toolbox/tree/84cb3057e5f6c54d64369ddd285db1cb36237ee8/packages/cache-url,
79 // using the widget at
80 // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#url-format.
82 "000000000000000000000000000000000000000000000000000000000000.com",
83 "stejanx4hsijaoj4secyecy4nvqodk56kw72whwcmvdbtucibf5a",
86 "00000000000000000000000000000000000000000000000000000000000a.com",
87 "jdcvbsorpnc3hcjrhst56nfm6ymdpovlawdbm2efyxpvlt4cpbya",
90 "00000000000000000000000000000000000000000000000000000000000\u03bb.com",
91 "qhzqeumjkfpcpuic3vqruyjswcr7y7gcm3crqyhhywvn3xrhchfa",
93 } {
94 output := domainPrefixFallback(test.domain)
95 if output != test.expected {
96 t.Errorf("%+q → %+q, expected %+q",
97 test.domain, output, test.expected)
102 // Checks that domainPrefix chooses domainPrefixBasic or domainPrefixFallback as
103 // appropriate; i.e., always returns string that is a valid DNS label and is
104 // IDNA-decodable.
105 func TestDomainPrefix(t *testing.T) {
106 // A validating IDNA profile, which checks label length and that the
107 // label contains only certain ASCII characters. It does not do the
108 // ValidateLabels check, because that depends on the input having
109 // certain properties.
110 profile := idna.New(
111 idna.VerifyDNSLength(true),
112 idna.StrictDomainName(true),
114 for _, domain := range []string{
115 "example.com",
116 "\u0314example.com",
117 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // 63 bytes
118 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // 64 bytes
119 "xn--57hw060o.com",
120 "a b c",
122 output := domainPrefix(domain)
123 if bytes.IndexByte([]byte(output), '.') != -1 {
124 t.Errorf("%+q → %+q contains a dot", domain, output)
126 _, err := profile.ToUnicode(output)
127 if err != nil {
128 t.Errorf("%+q → error %v", domain, err)
133 func mustParseURL(rawurl string) *url.URL {
134 u, err := url.Parse(rawurl)
135 if err != nil {
136 panic(err)
138 return u
141 func TestCacheURL(t *testing.T) {
142 // Tests expecting no error.
143 for _, test := range []struct {
144 pub string
145 cache string
146 contentType string
147 expected string
149 // With or without trailing slash on pubURL.
151 "http://example.com/",
152 "https://amp.cache/",
153 "c",
154 "https://example-com.amp.cache/c/example.com",
157 "http://example.com",
158 "https://amp.cache/",
159 "c",
160 "https://example-com.amp.cache/c/example.com",
162 // https pubURL.
164 "https://example.com/",
165 "https://amp.cache/",
166 "c",
167 "https://example-com.amp.cache/c/s/example.com",
169 // The content type should be escaped if necessary.
171 "http://example.com/",
172 "https://amp.cache/",
173 "/",
174 "https://example-com.amp.cache/%2F/example.com",
176 // Retain pubURL path, query, and fragment, including escaping.
178 "http://example.com/my%2Fpath/index.html?a=1#fragment",
179 "https://amp.cache/",
180 "c",
181 "https://example-com.amp.cache/c/example.com/my%2Fpath/index.html?a=1#fragment",
183 // Retain scheme, userinfo, port, and path of cacheURL, escaping
184 // whatever is necessary.
186 "http://example.com",
187 "http://cache%2Fuser:cache%40pass@amp.cache:123/with/../../path/..%2f../",
188 "c",
189 "http://cache%2Fuser:cache%40pass@example-com.amp.cache:123/path/..%2f../c/example.com",
191 // Port numbers in pubURL are allowed, if they're the default
192 // for scheme.
194 "http://example.com:80/",
195 "https://amp.cache/",
196 "c",
197 "https://example-com.amp.cache/c/example.com",
200 "https://example.com:443/",
201 "https://amp.cache/",
202 "c",
203 "https://example-com.amp.cache/c/s/example.com",
205 // "?" at the end of cacheURL is okay, as long as the query is
206 // empty.
208 "http://example.com/",
209 "https://amp.cache/?",
210 "c",
211 "https://example-com.amp.cache/c/example.com",
214 // https://developers.google.com/amp/cache/overview#example-requesting-document-using-tls
216 "https://example.com/amp_document.html",
217 "https://cdn.ampproject.org/",
218 "c",
219 "https://example-com.cdn.ampproject.org/c/s/example.com/amp_document.html",
221 // https://developers.google.com/amp/cache/overview#example-requesting-image-using-plain-http
223 "http://example.com/logo.png",
224 "https://cdn.ampproject.org/",
225 "i",
226 "https://example-com.cdn.ampproject.org/i/example.com/logo.png",
228 // https://developers.google.com/amp/cache/overview#query-parameter-example
230 "https://example.com/g?value=Hello%20World",
231 "https://cdn.ampproject.org/",
232 "c",
233 "https://example-com.cdn.ampproject.org/c/s/example.com/g?value=Hello%20World",
236 pubURL := mustParseURL(test.pub)
237 cacheURL := mustParseURL(test.cache)
238 outputURL, err := CacheURL(pubURL, cacheURL, test.contentType)
239 if err != nil {
240 t.Errorf("%+q %+q %+q → error %v",
241 test.pub, test.cache, test.contentType, err)
242 continue
244 if outputURL.String() != test.expected {
245 t.Errorf("%+q %+q %+q → %+q, expected %+q",
246 test.pub, test.cache, test.contentType, outputURL, test.expected)
247 continue
251 // Tests expecting an error.
252 for _, test := range []struct {
253 pub string
254 cache string
255 contentType string
257 // Empty content type.
259 "http://example.com/",
260 "https://amp.cache/",
263 // Empty host.
265 "http:///index.html",
266 "https://amp.cache/",
267 "c",
269 // Empty scheme.
271 "//example.com/",
272 "https://amp.cache/",
273 "c",
275 // Unrecognized scheme.
277 "ftp://example.com/",
278 "https://amp.cache/",
279 "c",
281 // Wrong port number for scheme.
283 "http://example.com:443/",
284 "https://amp.cache/",
285 "c",
287 // userinfo in pubURL.
289 "http://user@example.com/",
290 "https://amp.cache/",
291 "c",
294 "http://user:pass@example.com/",
295 "https://amp.cache/",
296 "c",
298 // cacheURL may not contain a query.
300 "http://example.com/",
301 "https://amp.cache/?a=1",
302 "c",
304 // cacheURL may not contain a fragment.
306 "http://example.com/",
307 "https://amp.cache/#fragment",
308 "c",
311 pubURL := mustParseURL(test.pub)
312 cacheURL := mustParseURL(test.cache)
313 outputURL, err := CacheURL(pubURL, cacheURL, test.contentType)
314 if err == nil {
315 t.Errorf("%+q %+q %+q → %+q, expected error",
316 test.pub, test.cache, test.contentType, outputURL)
317 continue