github.com/blend/go-sdk@v1.20220411.3/web/app_test.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package web 9 10 import ( 11 "bytes" 12 "context" 13 "fmt" 14 "net/http" 15 "strings" 16 "sync" 17 "testing" 18 "time" 19 20 "github.com/blend/go-sdk/assert" 21 "github.com/blend/go-sdk/env" 22 "github.com/blend/go-sdk/ex" 23 "github.com/blend/go-sdk/graceful" 24 "github.com/blend/go-sdk/logger" 25 "github.com/blend/go-sdk/webutil" 26 ) 27 28 // assert an app is graceful 29 var ( 30 _ graceful.Graceful = (*App)(nil) 31 ) 32 33 func controllerNoOp(_ *Ctx) Result { return nil } 34 35 type testController struct { 36 callback func(app *App) 37 } 38 39 func (tc testController) Register(app *App) { 40 if tc.callback != nil { 41 tc.callback(app) 42 } 43 } 44 45 func TestAppNew(t *testing.T) { 46 assert := assert.New(t) 47 48 app, err := New() 49 assert.Nil(err) 50 assert.NotNil(app.BaseState) 51 assert.NotNil(app.Views) 52 assert.Equal(http.SameSiteLaxMode, app.Auth.CookieDefaults.SameSite) 53 } 54 55 func TestAppNewFromConfig(t *testing.T) { 56 assert := assert.New(t) 57 58 app, err := New(OptConfig(Config{ 59 BindAddr: ":5555", 60 Port: 5000, 61 HandleMethodNotAllowed: true, 62 HandleOptions: true, 63 DisablePanicRecovery: true, 64 65 MaxHeaderBytes: 128, 66 ReadHeaderTimeout: 5 * time.Second, 67 ReadTimeout: 6 * time.Second, 68 IdleTimeout: 7 * time.Second, 69 WriteTimeout: 8 * time.Second, 70 71 CookieName: "A GOOD ONE", 72 Views: ViewCacheConfig{ 73 LiveReload: true, 74 }, 75 })) 76 77 assert.Nil(err) 78 79 assert.Equal(":5555", app.Config.BindAddr) 80 assert.True(app.Config.HandleMethodNotAllowed) 81 assert.True(app.Config.HandleOptions) 82 assert.True(app.Config.DisablePanicRecovery) 83 assert.Equal(128, app.Config.MaxHeaderBytes) 84 assert.Equal(5*time.Second, app.Config.ReadHeaderTimeout) 85 assert.Equal(6*time.Second, app.Config.ReadTimeout) 86 assert.Equal(7*time.Second, app.Config.IdleTimeout) 87 assert.Equal(8*time.Second, app.Config.WriteTimeout) 88 assert.Equal("A GOOD ONE", app.Auth.CookieDefaults.Name, "we should use the auth config for the auth manager") 89 assert.True(app.Views.LiveReload, "we should use the view cache config for the view cache") 90 } 91 92 func TestAppRegister(t *testing.T) { 93 assert := assert.New(t) 94 called := false 95 c := &testController{ 96 callback: func(_ *App) { 97 called = true 98 }, 99 } 100 app, err := New() 101 102 assert.Nil(err) 103 assert.False(called) 104 app.Register(c) 105 assert.True(called) 106 } 107 108 func TestAppPathParams(t *testing.T) { 109 assert := assert.New(t) 110 111 var route *Route 112 var params RouteParameters 113 app, err := New() 114 assert.Nil(err) 115 app.GET("/:uuid", func(c *Ctx) Result { 116 route = c.Route 117 params = c.RouteParams 118 return Raw([]byte("ok!")) 119 }) 120 121 route, params, skipSlashRedirect := app.Lookup("GET", "/foo") 122 assert.NotNil(route) 123 assert.NotEmpty(params) 124 assert.Equal("foo", params.Get("uuid")) 125 assert.False(skipSlashRedirect) 126 127 meta, err := MockGet(app, "/foo").Discard() 128 assert.Nil(err, fmt.Sprintf("%+v", err)) 129 assert.Equal(http.StatusOK, meta.StatusCode) 130 assert.NotNil(route) 131 assert.Equal("GET", route.Method) 132 assert.Equal("/:uuid", route.Path) 133 assert.NotNil(route.Handler) 134 135 assert.NotEmpty(params) 136 assert.Equal("foo", params.Get("uuid")) 137 } 138 139 func TestAppPathParamsForked(t *testing.T) { 140 /* 141 this test should assert that we can have a common structure of routes 142 namely that you can have some shared prefix but differentiate by plural. 143 */ 144 145 assert := assert.New(t) 146 147 var route *Route 148 var params RouteParameters 149 app, err := New() 150 assert.Nil(err) 151 app.GET("/foo/:uuid", func(c *Ctx) Result { return NoContent }) 152 app.GET("/foos/bar/:uuid", func(c *Ctx) Result { 153 route = c.Route 154 params = c.RouteParams 155 return Raw([]byte("ok!")) 156 }) 157 158 meta, err := MockGet(app, "/foos/bar/foo").Discard() 159 assert.Nil(err) 160 assert.Equal(http.StatusOK, meta.StatusCode) 161 assert.NotNil(route) 162 assert.Equal("GET", route.Method) 163 assert.Equal("/foos/bar/:uuid", route.Path) 164 assert.NotNil(route.Handler) 165 166 assert.NotNil(params) 167 assert.NotEmpty(params) 168 assert.Equal("foo", params.Get("uuid")) 169 } 170 171 func TestAppSetLogger(t *testing.T) { 172 assert := assert.New(t) 173 174 log := logger.MustNew() 175 app, err := New(OptLog(log)) 176 assert.Nil(err) 177 assert.NotNil(app.Log) 178 } 179 180 func TestAppCreateStaticMountedRoute(t *testing.T) { 181 assert := assert.New(t) 182 app, err := New() 183 assert.Nil(err) 184 assert.Equal("/testPath/*filepath", app.formatStaticMountRoute("/testPath/*filepath")) 185 assert.Equal("/testPath/*filepath", app.formatStaticMountRoute("/testPath/")) 186 assert.Equal("/testPath/*filepath", app.formatStaticMountRoute("/testPath")) 187 } 188 189 func TestAppStaticRewrite(t *testing.T) { 190 assert := assert.New(t) 191 app, err := New() 192 assert.Nil(err) 193 194 app.ServeStatic("/testPath", []string{"_static"}) 195 assert.NotEmpty(app.Statics) 196 assert.NotNil(app.Statics["/testPath/*filepath"]) 197 assert.Nil(app.SetStaticRewriteRule("/testPath", "(.*)", func(path string, pieces ...string) string { 198 return path 199 })) 200 assert.NotNil(app.SetStaticRewriteRule("/notapath", "(.*)", func(path string, pieces ...string) string { 201 return path 202 })) 203 204 assert.NotEmpty(app.Statics["/testPath/*filepath"].RewriteRules) 205 } 206 207 func TestAppStaticRewriteBadExp(t *testing.T) { 208 assert := assert.New(t) 209 app, err := New() 210 assert.Nil(err) 211 212 app.ServeStatic("/testPath", []string{"_static"}) 213 assert.NotEmpty(app.Statics) 214 assert.NotNil(app.Statics["/testPath/*filepath"]) 215 216 err = app.SetStaticRewriteRule("/testPath", "((((", func(path string, pieces ...string) string { 217 return path 218 }) 219 220 assert.NotNil(err) 221 assert.Empty(app.Statics["/testPath/*filepath"].RewriteRules) 222 } 223 224 func TestAppStaticHeader(t *testing.T) { 225 assert := assert.New(t) 226 app, err := New() 227 assert.Nil(err) 228 229 app.ServeStatic("/testPath", []string{"_static"}) 230 assert.NotEmpty(app.Statics) 231 assert.NotNil(app.Statics["/testPath/*filepath"]) 232 assert.Nil(app.SetStaticHeader("/testPath/*filepath", "cache-control", "haha what is caching.")) 233 assert.NotNil(app.SetStaticHeader("/notaroute", "cache-control", "haha what is caching.")) 234 assert.NotEmpty(app.Statics["/testPath/*filepath"].Headers) 235 } 236 237 func TestAppMiddleWarePipeline(t *testing.T) { 238 assert := assert.New(t) 239 240 didRun := false 241 242 app, err := New() 243 assert.Nil(err) 244 245 app.GET("/", 246 func(r *Ctx) Result { return Raw([]byte("OK!")) }, 247 func(action Action) Action { 248 didRun = true 249 return action 250 }, 251 func(action Action) Action { 252 return func(r *Ctx) Result { 253 return Raw([]byte("foo")) 254 } 255 }, 256 ) 257 258 result, _, err := MockGet(app, "/").Bytes() 259 assert.Nil(err) 260 assert.True(didRun) 261 assert.Equal("foo", string(result)) 262 } 263 264 func TestAppStatic(t *testing.T) { 265 assert := assert.New(t) 266 267 app, err := New() 268 assert.Nil(err) 269 270 app.ServeStatic("/static/*filepath", []string{"testdata"}) 271 272 index, _, err := MockGet(app, "/static/test_file.html").Bytes() 273 assert.Nil(err) 274 assert.True(strings.Contains(string(index), "Test!"), string(index)) 275 } 276 277 func TestAppStaticSingleFile(t *testing.T) { 278 assert := assert.New(t) 279 app, err := New() 280 assert.Nil(err) 281 282 app.GET("/", func(r *Ctx) Result { 283 return Static("testdata/test_file.html") 284 }) 285 286 index, _, err := MockGet(app, "/").Bytes() 287 assert.Nil(err) 288 assert.True(strings.Contains(string(index), "Test!"), string(index)) 289 } 290 291 func TestAppProviderMiddleware(t *testing.T) { 292 assert := assert.New(t) 293 294 var okAction = func(r *Ctx) Result { 295 assert.NotNil(r.DefaultProvider) 296 return Raw([]byte("OK!")) 297 } 298 299 app, err := New() 300 assert.Nil(err) 301 302 app.GET("/", okAction, JSONProviderAsDefault) 303 304 _, err = MockGet(app, "/").Discard() 305 assert.Nil(err) 306 } 307 308 func TestAppProviderMiddlewareOrder(t *testing.T) { 309 assert := assert.New(t) 310 311 app, err := New() 312 assert.Nil(err) 313 314 var okAction = func(r *Ctx) Result { 315 assert.NotNil(r.DefaultProvider) 316 return Raw([]byte("OK!")) 317 } 318 319 var dependsOnProvider = func(action Action) Action { 320 return func(r *Ctx) Result { 321 assert.NotNil(r.DefaultProvider) 322 return action(r) 323 } 324 } 325 326 app.GET("/", okAction, dependsOnProvider, JSONProviderAsDefault) 327 _, err = MockGet(app, "/").Discard() 328 assert.Nil(err) 329 } 330 331 func TestAppDefaultResultProviderWithDefault(t *testing.T) { 332 assert := assert.New(t) 333 334 app, err := New(OptUse(ViewProviderAsDefault)) 335 assert.Nil(err) 336 assert.NotEmpty(app.BaseMiddleware) 337 338 rc := NewCtx(nil, nil, app.ctxOptions(context.Background(), nil, nil)...) 339 340 // this will be set to the default initially 341 assert.NotNil(rc.DefaultProvider) 342 343 app.GET("/", func(ctx *Ctx) Result { 344 assert.NotNil(ctx.DefaultProvider) 345 _, isTyped := ctx.DefaultProvider.(*ViewCache) 346 assert.True(isTyped) 347 return nil 348 }) 349 _, err = MockGet(app, "/").Discard() 350 assert.Nil(err) 351 } 352 353 func TestAppDefaultResultProviderWithDefaultFromRoute(t *testing.T) { 354 assert := assert.New(t) 355 356 app, err := New(OptUse(JSONProviderAsDefault)) 357 assert.Nil(err) 358 359 app.Views.AddLiterals(DefaultTemplateNotAuthorized) 360 app.GET("/", controllerNoOp, SessionRequired, ViewProviderAsDefault) 361 362 //somehow assert that the content type is html 363 meta, err := MockGet(app, "/").Discard() 364 assert.Nil(err) 365 defer meta.Body.Close() 366 367 assert.Equal(webutil.ContentTypeHTML, meta.Header.Get(webutil.HeaderContentType)) 368 } 369 370 func TestAppViewResult(t *testing.T) { 371 assert := assert.New(t) 372 373 app, err := New() 374 assert.Nil(err) 375 376 app.Views.AddPaths("testdata/test_file.html") 377 app.GET("/", func(r *Ctx) Result { 378 return r.Views.View("test", "foobarbaz") 379 }) 380 381 contents, meta, err := MockGet(app, "/").Bytes() 382 assert.Nil(err) 383 assert.Equal(http.StatusOK, meta.StatusCode, string(contents)) 384 assert.Equal(webutil.ContentTypeHTML, meta.Header.Get(webutil.HeaderContentType)) 385 assert.Contains(string(contents), "foobarbaz") 386 } 387 388 func TestAppWritesLogsByDefault(t *testing.T) { 389 assert := assert.New(t) 390 391 buffer := bytes.NewBuffer(nil) 392 agent := logger.MustNew( 393 logger.OptAll(), 394 logger.OptFormatter( 395 logger.NewTextOutputFormatter( 396 logger.OptTextHideTimestamp(), 397 logger.OptTextNoColor(), 398 ), 399 ), 400 logger.OptOutput(buffer), 401 ) 402 403 app, err := New( 404 OptLog(agent), 405 ) 406 assert.Nil(err) 407 408 app.GET("/", func(r *Ctx) Result { 409 return Raw([]byte("ok!")) 410 }) 411 _, err = MockGet(app, "/").Discard() 412 assert.Nil(err) 413 agent.Drain() 414 assert.NotZero(buffer.Len()) 415 assert.NotEmpty(buffer.String()) 416 417 assert.Matches(`\[http.request\] 127\.0\.0\.1 GET \/ 200 (.*) text\/plain; charset=utf-8 3B web.route=\/\n`, buffer.String(), "buffer should contain the non-zero status code") // we use a prefix here because the elapsed time is variable. 418 } 419 420 func TestAppBindAddr(t *testing.T) { 421 assert := assert.New(t) 422 423 env.Env().Set("BIND_ADDR", ":9999") 424 env.Env().Set("PORT", "1111") 425 defer env.Restore() 426 427 assert.Equal(":3333", MustNew(OptBindAddr(":3333")).Config.BindAddr) 428 assert.Equal(":2222", MustNew(OptPort(2222)).Config.BindAddr) 429 } 430 431 func TestAppNotFound(t *testing.T) { 432 assert := assert.New(t) 433 434 app, err := New() 435 assert.Nil(err) 436 437 app.GET("/", func(r *Ctx) Result { 438 return Raw([]byte("ok!")) 439 }) 440 441 wg := sync.WaitGroup{} 442 wg.Add(1) 443 444 app.NotFoundHandler = app.RenderAction(func(r *Ctx) Result { 445 defer wg.Done() 446 return JSON.NotFound() 447 }) 448 _, err = MockGet(app, "/doesntexist").Discard() 449 assert.Nil(err) 450 wg.Wait() 451 } 452 453 func TestAppDefaultHeaders(t *testing.T) { 454 assert := assert.New(t) 455 app, err := New(OptDefaultHeader("foo", "bar"), OptDefaultHeader("baz", "buzz")) 456 assert.Nil(err) 457 assert.Equal([]string{"buzz"}, app.BaseHeaders[http.CanonicalHeaderKey("baz")]) 458 459 app.GET("/", func(r *Ctx) Result { 460 return Text.Result("ok") 461 }) 462 463 meta, err := MockGet(app, "/").Discard() 464 assert.Nil(err) 465 assert.NotEmpty(meta.Header) 466 assert.Equal("bar", meta.Header.Get("foo")) 467 assert.Equal("buzz", meta.Header.Get("baz")) 468 } 469 470 func TestAppViewErrorsRenderErrorView(t *testing.T) { 471 assert := assert.New(t) 472 473 app, err := New() 474 assert.Nil(err) 475 assert.NotNil(app.Views) 476 477 app.Views.AddLiterals(`{{ define "malformed" }} {{ .Ctx ALSKADJALSKDJA }} {{ end }}`) 478 app.GET("/", func(r *Ctx) Result { 479 return r.Views.View("malformed", nil) 480 }) 481 _, err = MockGet(app, "/").Discard() 482 assert.NotNil(err) 483 } 484 485 func TestAppAddsDefaultHeaders(t *testing.T) { 486 assert := assert.New(t) 487 488 app, err := New(OptBindAddr(DefaultMockBindAddr)) 489 assert.Nil(err) 490 491 app.GET("/", func(r *Ctx) Result { 492 return Text.Result("OK!") 493 }) 494 495 go func() { _ = app.Start() }() 496 <-app.NotifyStarted() 497 defer func() { _ = app.Stop() }() 498 499 res, err := http.Get("http://" + app.Listener.Addr().String() + "/") 500 assert.Nil(err) 501 assert.NotEmpty(res.Header) 502 assert.Equal(PackageName, res.Header.Get(webutil.HeaderServer)) 503 } 504 505 func TestAppHandlesPanics(t *testing.T) { 506 assert := assert.New(t) 507 508 app, err := New(OptBindAddr(DefaultMockBindAddr)) 509 assert.Nil(err) 510 511 app.GET("/", func(r *Ctx) Result { 512 panic("this is only a test") 513 }) 514 515 var didRecover bool 516 go func() { 517 defer func() { 518 if r := recover(); r != nil { 519 didRecover = true 520 } 521 }() 522 _ = app.Start() 523 }() 524 defer func() { _ = app.Stop() }() 525 <-app.NotifyStarted() 526 527 res, err := http.Get("http://" + app.Listener.Addr().String() + "/") 528 assert.Nil(err) 529 assert.Equal(http.StatusInternalServerError, res.StatusCode) 530 assert.False(didRecover) 531 } 532 533 var ( 534 _ Tracer = (*mockTracer)(nil) 535 _ ViewTracer = (*mockTracer)(nil) 536 ) 537 538 type mockTracer struct { 539 OnStart func(*Ctx) 540 OnFinish func(*Ctx, error) 541 542 OnViewStart func(*Ctx, *ViewResult) 543 OnViewFinish func(*Ctx, *ViewResult, error) 544 } 545 546 func (mt mockTracer) Start(ctx *Ctx) TraceFinisher { 547 if mt.OnStart != nil { 548 mt.OnStart(ctx) 549 } 550 return &mockTraceFinisher{parent: &mt} 551 } 552 553 func (mt mockTracer) StartView(ctx *Ctx, vr *ViewResult) ViewTraceFinisher { 554 if mt.OnViewStart != nil { 555 mt.OnViewStart(ctx, vr) 556 } 557 return &mockViewTraceFinisher{parent: &mt} 558 } 559 560 type mockTraceFinisher struct { 561 parent *mockTracer 562 } 563 564 func (mtf mockTraceFinisher) Finish(ctx *Ctx, err error) { 565 mtf.parent.OnFinish(ctx, err) 566 } 567 568 type mockViewTraceFinisher struct { 569 parent *mockTracer 570 } 571 572 func (mvf mockViewTraceFinisher) FinishView(ctx *Ctx, vr *ViewResult, err error) { 573 mvf.parent.OnViewFinish(ctx, vr, err) 574 } 575 576 func ok(_ *Ctx) Result { return JSON.OK() } 577 func doPanic(_ *Ctx) Result { panic("this is only a test") } 578 func internalError(_ *Ctx) Result { return JSON.InternalError(fmt.Errorf("only a test")) } 579 func viewOK(ctx *Ctx) Result { return ctx.Views.View("ok", nil) } 580 581 func TestAppTracer(t *testing.T) { 582 assert := assert.New(t) 583 584 wg := sync.WaitGroup{} 585 wg.Add(2) 586 587 var hasValue bool 588 589 app, err := New() 590 assert.Nil(err) 591 592 app.GET("/", ok) 593 app.Tracer = mockTracer{ 594 OnStart: func(ctx *Ctx) { 595 defer wg.Done() 596 ctx.WithStateValue("foo", "bar") 597 }, 598 OnFinish: func(ctx *Ctx, err error) { 599 defer wg.Done() 600 hasValue = ctx.StateValue("foo") != nil 601 }, 602 } 603 604 _, err = MockGet(app, "/").Discard() 605 assert.Nil(err) 606 wg.Wait() 607 608 assert.True(hasValue) 609 } 610 611 func TestAppTracerError(t *testing.T) { 612 assert := assert.New(t) 613 614 wg := sync.WaitGroup{} 615 wg.Add(1) 616 617 var hasError bool 618 619 app, err := New() 620 assert.Nil(err) 621 622 app.GET("/", ok) 623 app.GET("/error", internalError) 624 app.Tracer = mockTracer{ 625 OnFinish: func(ctx *Ctx, err error) { 626 defer wg.Done() 627 hasError = err != nil 628 }, 629 } 630 631 _, err = MockGet(app, "/error").Discard() 632 assert.Nil(err) 633 wg.Wait() 634 assert.True(hasError) 635 } 636 637 func TestAppViewTracer(t *testing.T) { 638 assert := assert.New(t) 639 640 wg := sync.WaitGroup{} 641 wg.Add(4) 642 643 var hasValue bool 644 645 app, err := New() 646 assert.Nil(err) 647 648 app.Views.AddLiterals("{{ define \"ok\" }}ok{{end}}") 649 assert.Nil(app.Views.Initialize()) 650 651 app.GET("/", ok) 652 app.GET("/view", viewOK) 653 app.Tracer = mockTracer{ 654 OnStart: func(_ *Ctx) { wg.Done() }, 655 OnFinish: func(_ *Ctx, _ error) { wg.Done() }, 656 OnViewStart: func(ctx *Ctx, vr *ViewResult) { 657 defer wg.Done() 658 hasValue = vr.ViewName == "ok" 659 }, 660 OnViewFinish: func(ctx *Ctx, vr *ViewResult, err error) { 661 defer wg.Done() 662 }, 663 } 664 665 _, err = MockGet(app, "/view").Discard() 666 assert.Nil(err) 667 wg.Wait() 668 669 assert.True(hasValue) 670 } 671 672 func TestAppViewTracerError(t *testing.T) { 673 assert := assert.New(t) 674 675 wg := sync.WaitGroup{} 676 wg.Add(4) 677 678 var hasValue, hasError, hasViewError bool 679 680 app, err := New() 681 assert.Nil(err) 682 683 app.Views.AddLiterals("{{ define \"ok\" }}{{template \"fake\"}}ok{{end}}") 684 app.GET("/view", viewOK) 685 app.Tracer = mockTracer{ 686 OnStart: func(_ *Ctx) { wg.Done() }, 687 OnFinish: func(_ *Ctx, err error) { 688 defer wg.Done() 689 hasError = err != nil 690 }, 691 OnViewStart: func(ctx *Ctx, vr *ViewResult) { 692 defer wg.Done() 693 hasValue = vr.ViewName == "ok" 694 }, 695 OnViewFinish: func(ctx *Ctx, vr *ViewResult, err error) { 696 defer wg.Done() 697 hasViewError = err != nil 698 }, 699 } 700 _, err = MockGet(app, "/view").Discard() 701 assert.Nil(err) 702 wg.Wait() 703 704 assert.True(hasValue) 705 assert.True(hasError) 706 assert.True(hasViewError) 707 } 708 709 func TestAppNilLoggerPanic(t *testing.T) { 710 assert := assert.New(t) 711 712 app, err := New(OptLog(nil)) 713 assert.Nil(err) 714 app.PanicAction = func(r *Ctx, err interface{}) Result { 715 return r.DefaultProvider.InternalError(ex.New(err)) 716 } 717 assert.Nil(err) 718 app.GET("/", doPanic, ViewProviderAsDefault) 719 720 res, err := MockGet(app, "/").Discard() 721 assert.Nil(err) 722 assert.Equal(http.StatusInternalServerError, res.StatusCode) 723 } 724 725 func TestAppContextRequestStarted(t *testing.T) { 726 assert := assert.New(t) 727 728 app, err := New() 729 assert.Nil(err) 730 731 var hadRequestStarted bool 732 app.GET("/", func(r *Ctx) Result { 733 hadRequestStarted = !GetRequestStarted(r.Context()).IsZero() 734 return nil 735 }) 736 737 _, err = MockGet(app, "/").Discard() 738 assert.Nil(err) 739 assert.True(hadRequestStarted) 740 } 741 742 func TestAppMethodBare(t *testing.T) { 743 assert := assert.New(t) 744 745 buffer := bytes.NewBuffer(nil) 746 agent := logger.MustNew( 747 logger.OptAll(), 748 logger.OptFormatter( 749 logger.NewTextOutputFormatter( 750 logger.OptTextHideTimestamp(), 751 logger.OptTextNoColor(), 752 ), 753 ), 754 logger.OptOutput(buffer), 755 ) 756 757 app, err := New( 758 OptLog(agent), 759 ) 760 assert.Nil(err) 761 762 app.MethodBare(webutil.MethodGet, "/", func(r *Ctx) Result { 763 return Raw([]byte("ok!")) 764 }) 765 _, err = MockGet(app, "/").Discard() 766 assert.Nil(err) 767 agent.Drain() 768 assert.Empty(buffer.String()) 769 }