github.com/hairyhenderson/gomplate/v4@v4.0.0-pre-2.0.20240520121557-362f058f0c93/data/datasource_test.go (about) 1 package data 2 3 import ( 4 "context" 5 "net/http" 6 "net/http/httptest" 7 "net/url" 8 "os" 9 "runtime" 10 "testing" 11 "testing/fstest" 12 13 "github.com/hairyhenderson/go-fsimpl" 14 "github.com/hairyhenderson/go-fsimpl/httpfs" 15 "github.com/hairyhenderson/gomplate/v4/internal/config" 16 "github.com/hairyhenderson/gomplate/v4/internal/datafs" 17 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 ) 21 22 const osWindows = "windows" 23 24 func mustParseURL(in string) *url.URL { 25 u, _ := url.Parse(in) 26 return u 27 } 28 29 func TestNewData(t *testing.T) { 30 d, err := NewData(nil, nil) 31 require.NoError(t, err) 32 assert.Empty(t, d.Sources) 33 34 d, err = NewData([]string{"foo=http:///foo.json"}, nil) 35 require.NoError(t, err) 36 assert.Equal(t, "/foo.json", d.Sources["foo"].URL.Path) 37 38 d, err = NewData([]string{"foo=http:///foo.json"}, []string{}) 39 require.NoError(t, err) 40 assert.Equal(t, "/foo.json", d.Sources["foo"].URL.Path) 41 assert.Empty(t, d.Sources["foo"].Header) 42 43 d, err = NewData([]string{"foo=http:///foo.json"}, []string{"bar=Accept: blah"}) 44 require.NoError(t, err) 45 assert.Equal(t, "/foo.json", d.Sources["foo"].URL.Path) 46 assert.Empty(t, d.Sources["foo"].Header) 47 48 d, err = NewData([]string{"foo=http:///foo.json"}, []string{"foo=Accept: blah"}) 49 require.NoError(t, err) 50 assert.Equal(t, "/foo.json", d.Sources["foo"].URL.Path) 51 assert.Equal(t, "blah", d.Sources["foo"].Header["Accept"][0]) 52 } 53 54 func TestDatasource(t *testing.T) { 55 setup := func(ext string, contents []byte) *Data { 56 fname := "foo." + ext 57 var uPath string 58 if runtime.GOOS == osWindows { 59 uPath = "C:/tmp/" + fname 60 } else { 61 uPath = "/tmp/" + fname 62 } 63 64 fsys := datafs.WrapWdFS(fstest.MapFS{ 65 "tmp/" + fname: &fstest.MapFile{Data: contents}, 66 }) 67 ctx := datafs.ContextWithFSProvider(context.Background(), datafs.WrappedFSProvider(fsys, "file", "")) 68 69 sources := map[string]config.DataSource{ 70 "foo": { 71 URL: &url.URL{Scheme: "file", Path: uPath}, 72 }, 73 } 74 return &Data{Sources: sources, Ctx: ctx} 75 } 76 77 test := func(ext, mime string, contents []byte, expected interface{}) { 78 data := setup(ext, contents) 79 80 actual, err := data.Datasource("foo", "?type="+mime) 81 require.NoError(t, err) 82 assert.Equal(t, expected, actual) 83 } 84 85 testObj := func(ext, mime string, contents []byte) { 86 test(ext, mime, contents, 87 map[string]interface{}{ 88 "hello": map[string]interface{}{"cruel": "world"}, 89 }) 90 } 91 92 testObj("json", jsonMimetype, []byte(`{"hello":{"cruel":"world"}}`)) 93 testObj("yml", yamlMimetype, []byte("hello:\n cruel: world\n")) 94 test("json", jsonMimetype, []byte(`[1, "two", true]`), 95 []interface{}{1, "two", true}) 96 test("yaml", yamlMimetype, []byte("---\n- 1\n- two\n- true\n"), 97 []interface{}{1, "two", true}) 98 99 d := setup("", nil) 100 actual, err := d.Datasource("foo") 101 require.NoError(t, err) 102 assert.Equal(t, "", actual) 103 104 _, err = d.Datasource("bar") 105 require.Error(t, err) 106 } 107 108 func TestDatasourceReachable(t *testing.T) { 109 fname := "foo.json" 110 var uPath string 111 if runtime.GOOS == osWindows { 112 uPath = "C:/tmp/" + fname 113 } else { 114 uPath = "/tmp/" + fname 115 } 116 117 fsys := datafs.WrapWdFS(fstest.MapFS{ 118 "tmp/" + fname: &fstest.MapFile{Data: []byte("{}")}, 119 }) 120 ctx := datafs.ContextWithFSProvider(context.Background(), datafs.WrappedFSProvider(fsys, "file", "")) 121 122 sources := map[string]config.DataSource{ 123 "foo": { 124 URL: &url.URL{Scheme: "file", Path: uPath}, 125 }, 126 "bar": { 127 URL: &url.URL{Scheme: "file", Path: "/bogus"}, 128 }, 129 } 130 data := &Data{Sources: sources, Ctx: ctx} 131 132 assert.True(t, data.DatasourceReachable("foo")) 133 assert.False(t, data.DatasourceReachable("bar")) 134 } 135 136 func TestDatasourceExists(t *testing.T) { 137 sources := map[string]config.DataSource{ 138 "foo": {}, 139 } 140 data := &Data{Sources: sources} 141 assert.True(t, data.DatasourceExists("foo")) 142 assert.False(t, data.DatasourceExists("bar")) 143 } 144 145 func TestInclude(t *testing.T) { 146 ext := "txt" 147 contents := "hello world" 148 fname := "foo." + ext 149 150 var uPath string 151 if runtime.GOOS == osWindows { 152 uPath = "C:/tmp/" + fname 153 } else { 154 uPath = "/tmp/" + fname 155 } 156 157 fsys := datafs.WrapWdFS(fstest.MapFS{ 158 "tmp/" + fname: &fstest.MapFile{Data: []byte(contents)}, 159 }) 160 ctx := datafs.ContextWithFSProvider(context.Background(), datafs.WrappedFSProvider(fsys, "file", "")) 161 162 sources := map[string]config.DataSource{ 163 "foo": { 164 URL: &url.URL{Scheme: "file", Path: uPath}, 165 }, 166 } 167 data := &Data{Sources: sources, Ctx: ctx} 168 actual, err := data.Include("foo") 169 require.NoError(t, err) 170 assert.Equal(t, contents, actual) 171 } 172 173 func TestDefineDatasource(t *testing.T) { 174 d := &Data{} 175 _, err := d.DefineDatasource("", "foo.json") 176 require.Error(t, err) 177 178 d = &Data{} 179 _, err = d.DefineDatasource("", "../foo.json") 180 require.Error(t, err) 181 182 d = &Data{} 183 _, err = d.DefineDatasource("", "ftp://example.com/foo.yml") 184 require.Error(t, err) 185 186 d = &Data{} 187 _, err = d.DefineDatasource("data", "foo.json") 188 s := d.Sources["data"] 189 require.NoError(t, err) 190 assert.EqualValues(t, &url.URL{Path: "foo.json"}, s.URL) 191 192 d = &Data{} 193 _, err = d.DefineDatasource("data", "/otherdir/foo.json") 194 s = d.Sources["data"] 195 require.NoError(t, err) 196 assert.Equal(t, "file", s.URL.Scheme) 197 assert.True(t, s.URL.IsAbs()) 198 assert.Equal(t, "/otherdir/foo.json", s.URL.Path) 199 200 d = &Data{} 201 _, err = d.DefineDatasource("data", "sftp://example.com/blahblah/foo.json") 202 s = d.Sources["data"] 203 require.NoError(t, err) 204 assert.Equal(t, "sftp", s.URL.Scheme) 205 assert.True(t, s.URL.IsAbs()) 206 assert.Equal(t, "/blahblah/foo.json", s.URL.Path) 207 208 d = &Data{ 209 Sources: map[string]config.DataSource{ 210 "data": {}, 211 }, 212 } 213 _, err = d.DefineDatasource("data", "/otherdir/foo.json") 214 s = d.Sources["data"] 215 require.NoError(t, err) 216 assert.Nil(t, s.URL) 217 218 d = &Data{} 219 _, err = d.DefineDatasource("data", "/otherdir/foo?type=application/x-env") 220 require.NoError(t, err) 221 s = d.Sources["data"] 222 require.NotNil(t, s) 223 assert.Equal(t, "/otherdir/foo", s.URL.Path) 224 } 225 226 func TestFromConfig(t *testing.T) { 227 ctx := context.Background() 228 229 cfg := &config.Config{} 230 actual := FromConfig(ctx, cfg) 231 expected := &Data{ 232 Ctx: actual.Ctx, 233 Sources: map[string]config.DataSource{}, 234 } 235 assert.EqualValues(t, expected, actual) 236 237 cfg = &config.Config{ 238 DataSources: map[string]config.DataSource{ 239 "foo": { 240 URL: mustParseURL("http://example.com"), 241 }, 242 }, 243 } 244 actual = FromConfig(ctx, cfg) 245 expected = &Data{ 246 Ctx: actual.Ctx, 247 Sources: map[string]config.DataSource{ 248 "foo": { 249 URL: mustParseURL("http://example.com"), 250 }, 251 }, 252 } 253 assert.EqualValues(t, expected, actual) 254 255 cfg = &config.Config{ 256 DataSources: map[string]config.DataSource{ 257 "foo": { 258 URL: mustParseURL("http://foo.com"), 259 }, 260 }, 261 Context: map[string]config.DataSource{ 262 "bar": { 263 URL: mustParseURL("http://bar.com"), 264 Header: http.Header{ 265 "Foo": []string{"bar"}, 266 }, 267 }, 268 }, 269 ExtraHeaders: map[string]http.Header{ 270 "baz": { 271 "Foo": []string{"bar"}, 272 }, 273 }, 274 } 275 actual = FromConfig(ctx, cfg) 276 expected = &Data{ 277 Ctx: actual.Ctx, 278 Sources: map[string]config.DataSource{ 279 "foo": { 280 URL: mustParseURL("http://foo.com"), 281 }, 282 "bar": { 283 URL: mustParseURL("http://bar.com"), 284 Header: http.Header{ 285 "Foo": []string{"bar"}, 286 }, 287 }, 288 }, 289 ExtraHeaders: map[string]http.Header{ 290 "baz": { 291 "Foo": []string{"bar"}, 292 }, 293 }, 294 } 295 assert.EqualValues(t, expected, actual) 296 } 297 298 func TestListDatasources(t *testing.T) { 299 sources := map[string]config.DataSource{ 300 "foo": {}, 301 "bar": {}, 302 } 303 data := &Data{Sources: sources} 304 305 assert.Equal(t, []string{"bar", "foo"}, data.ListDatasources()) 306 } 307 308 func TestResolveURL(t *testing.T) { 309 out, err := resolveURL(mustParseURL("http://example.com/foo.json"), "bar.json") 310 require.NoError(t, err) 311 assert.Equal(t, "http://example.com/bar.json", out.String()) 312 313 out, err = resolveURL(mustParseURL("http://example.com/a/b/?n=2"), "bar.json?q=1") 314 require.NoError(t, err) 315 assert.Equal(t, "http://example.com/a/b/bar.json?n=2&q=1", out.String()) 316 317 out, err = resolveURL(mustParseURL("git+file:///tmp/myrepo"), "//myfile?type=application/json") 318 require.NoError(t, err) 319 assert.Equal(t, "git+file:///tmp/myrepo//myfile?type=application/json", out.String()) 320 321 out, err = resolveURL(mustParseURL("git+file:///tmp/foo/bar/"), "//myfile?type=application/json") 322 require.NoError(t, err) 323 assert.Equal(t, "git+file:///tmp/foo/bar//myfile?type=application/json", out.String()) 324 325 out, err = resolveURL(mustParseURL("git+file:///tmp/myrepo/"), ".//myfile?type=application/json") 326 require.NoError(t, err) 327 assert.Equal(t, "git+file:///tmp/myrepo//myfile?type=application/json", out.String()) 328 329 out, err = resolveURL(mustParseURL("git+file:///tmp/repo//foo.txt"), "") 330 require.NoError(t, err) 331 assert.Equal(t, "git+file:///tmp/repo//foo.txt", out.String()) 332 333 out, err = resolveURL(mustParseURL("git+file:///tmp/myrepo"), ".//myfile?type=application/json") 334 require.NoError(t, err) 335 assert.Equal(t, "git+file:///tmp/myrepo//myfile?type=application/json", out.String()) 336 337 out, err = resolveURL(mustParseURL("git+file:///tmp/myrepo//foo/?type=application/json"), "bar/myfile") 338 require.NoError(t, err) 339 // note that the '/' in the query string is encoded to %2F - that's OK 340 assert.Equal(t, "git+file:///tmp/myrepo//foo/bar/myfile?type=application%2Fjson", out.String()) 341 342 // both base and relative may not contain "//" 343 _, err = resolveURL(mustParseURL("git+ssh://git@example.com/foo//bar"), ".//myfile") 344 require.Error(t, err) 345 346 _, err = resolveURL(mustParseURL("git+ssh://git@example.com/foo//bar"), "baz//myfile") 347 require.Error(t, err) 348 349 // relative urls must remain relative 350 out, err = resolveURL(mustParseURL("tmp/foo.json"), "") 351 require.NoError(t, err) 352 assert.Equal(t, "tmp/foo.json", out.String()) 353 } 354 355 func TestReadFileContent(t *testing.T) { 356 wd, _ := os.Getwd() 357 t.Cleanup(func() { 358 _ = os.Chdir(wd) 359 }) 360 _ = os.Chdir("/") 361 362 mux := http.NewServeMux() 363 mux.HandleFunc("/foo.json", func(w http.ResponseWriter, _ *http.Request) { 364 w.Header().Set("Content-Type", jsonMimetype) 365 w.Write([]byte(`{"foo": "bar"}`)) 366 }) 367 368 srv := httptest.NewServer(mux) 369 t.Cleanup(srv.Close) 370 371 fsys := datafs.WrapWdFS(fstest.MapFS{ 372 "foo.json": &fstest.MapFile{Data: []byte(`{"foo": "bar"}`)}, 373 "dir/1.yaml": &fstest.MapFile{Data: []byte(`foo: bar`)}, 374 "dir/2.yaml": &fstest.MapFile{Data: []byte(`baz: qux`)}, 375 "dir/sub/sub1.yaml": &fstest.MapFile{Data: []byte(`quux: corge`)}, 376 }) 377 378 fsp := fsimpl.NewMux() 379 fsp.Add(httpfs.FS) 380 fsp.Add(datafs.WrappedFSProvider(fsys, "file", "")) 381 382 ctx := datafs.ContextWithFSProvider(context.Background(), fsp) 383 384 d := Data{} 385 386 fc, err := d.readFileContent(ctx, mustParseURL("file:///foo.json"), nil) 387 require.NoError(t, err) 388 assert.Equal(t, []byte(`{"foo": "bar"}`), fc.b) 389 390 fc, err = d.readFileContent(ctx, mustParseURL("dir/"), nil) 391 require.NoError(t, err) 392 assert.JSONEq(t, `["1.yaml", "2.yaml", "sub"]`, string(fc.b)) 393 394 fc, err = d.readFileContent(ctx, mustParseURL(srv.URL+"/foo.json"), nil) 395 require.NoError(t, err) 396 assert.Equal(t, []byte(`{"foo": "bar"}`), fc.b) 397 }