data_test.go (8744B)
1 // Copyright 2017 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package data 15 16 import ( 17 "bytes" 18 "html/template" 19 "net/http" 20 "net/http/httptest" 21 "path/filepath" 22 "strings" 23 "testing" 24 25 "github.com/gohugoio/hugo/common/maps" 26 27 qt "github.com/frankban/quicktest" 28 ) 29 30 func TestGetCSV(t *testing.T) { 31 t.Parallel() 32 c := qt.New(t) 33 34 for i, test := range []struct { 35 sep string 36 url string 37 content string 38 expect any 39 }{ 40 // Remotes 41 { 42 ",", 43 `http://success/`, 44 "gomeetup,city\nyes,Sydney\nyes,San Francisco\nyes,Stockholm\n", 45 [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}, 46 }, 47 { 48 ",", 49 `http://error.extra.field/`, 50 "gomeetup,city\nyes,Sydney\nyes,San Francisco\nyes,Stockholm,EXTRA\n", 51 false, 52 }, 53 { 54 ",", 55 `http://nofound/404`, 56 ``, 57 false, 58 }, 59 60 // Locals 61 { 62 ";", 63 "pass/semi", 64 "gomeetup;city\nyes;Sydney\nyes;San Francisco\nyes;Stockholm\n", 65 [][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}}, 66 }, 67 { 68 ";", 69 "fail/no-file", 70 "", 71 false, 72 }, 73 } { 74 75 c.Run(test.url, func(c *qt.C) { 76 msg := qt.Commentf("Test %d", i) 77 78 ns := newTestNs() 79 80 // Setup HTTP test server 81 var srv *httptest.Server 82 srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) { 83 if !hasHeaderValue(r.Header, "Accept", "text/csv") && !hasHeaderValue(r.Header, "Accept", "text/plain") { 84 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 85 return 86 } 87 88 if r.URL.Path == "/404" { 89 http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 90 return 91 } 92 93 w.Header().Add("Content-type", "text/csv") 94 95 w.Write([]byte(test.content)) 96 }) 97 defer func() { srv.Close() }() 98 99 // Setup local test file for schema-less URLs 100 if !strings.Contains(test.url, ":") && !strings.HasPrefix(test.url, "fail/") { 101 f, err := ns.deps.Fs.Source.Create(filepath.Join(ns.deps.Cfg.GetString("workingDir"), test.url)) 102 c.Assert(err, qt.IsNil, msg) 103 f.WriteString(test.content) 104 f.Close() 105 } 106 107 // Get on with it 108 got, err := ns.GetCSV(test.sep, test.url) 109 110 if _, ok := test.expect.(bool); ok { 111 c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 1) 112 c.Assert(got, qt.IsNil) 113 return 114 } 115 116 c.Assert(err, qt.IsNil, msg) 117 c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0) 118 c.Assert(got, qt.Not(qt.IsNil), msg) 119 c.Assert(got, qt.DeepEquals, test.expect, msg) 120 }) 121 122 } 123 } 124 125 func TestGetJSON(t *testing.T) { 126 t.Parallel() 127 c := qt.New(t) 128 129 for i, test := range []struct { 130 url string 131 content string 132 expect any 133 }{ 134 { 135 `http://success/`, 136 `{"gomeetup":["Sydney","San Francisco","Stockholm"]}`, 137 map[string]any{"gomeetup": []any{"Sydney", "San Francisco", "Stockholm"}}, 138 }, 139 { 140 `http://malformed/`, 141 `{gomeetup:["Sydney","San Francisco","Stockholm"]}`, 142 false, 143 }, 144 { 145 `http://nofound/404`, 146 ``, 147 false, 148 }, 149 // Locals 150 { 151 "pass/semi", 152 `{"gomeetup":["Sydney","San Francisco","Stockholm"]}`, 153 map[string]any{"gomeetup": []any{"Sydney", "San Francisco", "Stockholm"}}, 154 }, 155 { 156 "fail/no-file", 157 "", 158 false, 159 }, 160 { 161 `pass/üńīçøðê-url.json`, 162 `{"gomeetup":["Sydney","San Francisco","Stockholm"]}`, 163 map[string]any{"gomeetup": []any{"Sydney", "San Francisco", "Stockholm"}}, 164 }, 165 } { 166 167 c.Run(test.url, func(c *qt.C) { 168 169 msg := qt.Commentf("Test %d", i) 170 ns := newTestNs() 171 172 // Setup HTTP test server 173 var srv *httptest.Server 174 srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) { 175 if !hasHeaderValue(r.Header, "Accept", "application/json") { 176 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 177 return 178 } 179 180 if r.URL.Path == "/404" { 181 http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 182 return 183 } 184 185 w.Header().Add("Content-type", "application/json") 186 187 w.Write([]byte(test.content)) 188 }) 189 defer func() { srv.Close() }() 190 191 // Setup local test file for schema-less URLs 192 if !strings.Contains(test.url, ":") && !strings.HasPrefix(test.url, "fail/") { 193 f, err := ns.deps.Fs.Source.Create(filepath.Join(ns.deps.Cfg.GetString("workingDir"), test.url)) 194 c.Assert(err, qt.IsNil, msg) 195 f.WriteString(test.content) 196 f.Close() 197 } 198 199 // Get on with it 200 got, _ := ns.GetJSON(test.url) 201 202 if _, ok := test.expect.(bool); ok { 203 c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 1) 204 return 205 } 206 207 c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0, msg) 208 c.Assert(got, qt.Not(qt.IsNil), msg) 209 c.Assert(got, qt.DeepEquals, test.expect) 210 211 }) 212 } 213 } 214 215 func TestHeaders(t *testing.T) { 216 t.Parallel() 217 c := qt.New(t) 218 219 for _, test := range []struct { 220 name string 221 headers any 222 assert func(c *qt.C, headers string) 223 }{ 224 { 225 `Misc header variants`, 226 map[string]any{ 227 "Accept-Charset": "utf-8", 228 "Max-forwards": "10", 229 "X-Int": 32, 230 "X-Templ": template.HTML("a"), 231 "X-Multiple": []string{"a", "b"}, 232 "X-MultipleInt": []int{3, 4}, 233 }, 234 func(c *qt.C, headers string) { 235 c.Assert(headers, qt.Contains, "Accept-Charset: utf-8") 236 c.Assert(headers, qt.Contains, "Max-Forwards: 10") 237 c.Assert(headers, qt.Contains, "X-Int: 32") 238 c.Assert(headers, qt.Contains, "X-Templ: a") 239 c.Assert(headers, qt.Contains, "X-Multiple: a") 240 c.Assert(headers, qt.Contains, "X-Multiple: b") 241 c.Assert(headers, qt.Contains, "X-Multipleint: 3") 242 c.Assert(headers, qt.Contains, "X-Multipleint: 4") 243 c.Assert(headers, qt.Contains, "User-Agent: Hugo Static Site Generator") 244 }, 245 }, 246 { 247 `Params`, 248 maps.Params{ 249 "Accept-Charset": "utf-8", 250 }, 251 func(c *qt.C, headers string) { 252 c.Assert(headers, qt.Contains, "Accept-Charset: utf-8") 253 }, 254 }, 255 { 256 `Override User-Agent`, 257 map[string]any{ 258 "User-Agent": "007", 259 }, 260 func(c *qt.C, headers string) { 261 c.Assert(headers, qt.Contains, "User-Agent: 007") 262 }, 263 }, 264 } { 265 266 c.Run(test.name, func(c *qt.C) { 267 268 ns := newTestNs() 269 270 // Setup HTTP test server 271 var srv *httptest.Server 272 var headers bytes.Buffer 273 srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) { 274 c.Assert(r.URL.String(), qt.Equals, "http://gohugo.io/api?foo") 275 w.Write([]byte("{}")) 276 r.Header.Write(&headers) 277 278 }) 279 defer func() { srv.Close() }() 280 281 testFunc := func(fn func(args ...any) error) { 282 defer headers.Reset() 283 err := fn("http://example.org/api", "?foo", test.headers) 284 285 c.Assert(err, qt.IsNil) 286 c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0) 287 test.assert(c, headers.String()) 288 } 289 290 testFunc(func(args ...any) error { 291 _, err := ns.GetJSON(args...) 292 return err 293 }) 294 testFunc(func(args ...any) error { 295 _, err := ns.GetCSV(",", args...) 296 return err 297 }) 298 299 }) 300 301 } 302 } 303 304 func TestToURLAndHeaders(t *testing.T) { 305 t.Parallel() 306 c := qt.New(t) 307 url, headers := toURLAndHeaders([]any{"https://foo?id=", 32}) 308 c.Assert(url, qt.Equals, "https://foo?id=32") 309 c.Assert(headers, qt.IsNil) 310 311 url, headers = toURLAndHeaders([]any{"https://foo?id=", 32, map[string]any{"a": "b"}}) 312 c.Assert(url, qt.Equals, "https://foo?id=32") 313 c.Assert(headers, qt.DeepEquals, map[string]any{"a": "b"}) 314 } 315 316 func TestParseCSV(t *testing.T) { 317 t.Parallel() 318 c := qt.New(t) 319 320 for i, test := range []struct { 321 csv []byte 322 sep string 323 exp string 324 err bool 325 }{ 326 {[]byte("a,b,c\nd,e,f\n"), "", "", true}, 327 {[]byte("a,b,c\nd,e,f\n"), "~/", "", true}, 328 {[]byte("a,b,c\nd,e,f"), "|", "a,b,cd,e,f", false}, 329 {[]byte("q,w,e\nd,e,f"), ",", "qwedef", false}, 330 {[]byte("a|b|c\nd|e|f|g"), "|", "abcdefg", true}, 331 {[]byte("z|y|c\nd|e|f"), "|", "zycdef", false}, 332 } { 333 msg := qt.Commentf("Test %d: %v", i, test) 334 335 csv, err := parseCSV(test.csv, test.sep) 336 if test.err { 337 c.Assert(err, qt.Not(qt.IsNil), msg) 338 continue 339 } 340 c.Assert(err, qt.IsNil, msg) 341 342 act := "" 343 for _, v := range csv { 344 act = act + strings.Join(v, "") 345 } 346 347 c.Assert(act, qt.Equals, test.exp, msg) 348 } 349 }