testrig.go (7538B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package testrig 19 20 import ( 21 "bytes" 22 "context" 23 "errors" 24 "fmt" 25 "io" 26 "net/http" 27 "os" 28 "os/signal" 29 "syscall" 30 31 "github.com/gin-gonic/gin" 32 "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" 33 "github.com/superseriousbusiness/gotosocial/internal/api" 34 apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" 35 "github.com/superseriousbusiness/gotosocial/internal/config" 36 "github.com/superseriousbusiness/gotosocial/internal/gotosocial" 37 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 38 "github.com/superseriousbusiness/gotosocial/internal/log" 39 "github.com/superseriousbusiness/gotosocial/internal/middleware" 40 "github.com/superseriousbusiness/gotosocial/internal/oidc" 41 tlprocessor "github.com/superseriousbusiness/gotosocial/internal/processing/timeline" 42 "github.com/superseriousbusiness/gotosocial/internal/state" 43 "github.com/superseriousbusiness/gotosocial/internal/storage" 44 "github.com/superseriousbusiness/gotosocial/internal/timeline" 45 "github.com/superseriousbusiness/gotosocial/internal/tracing" 46 "github.com/superseriousbusiness/gotosocial/internal/visibility" 47 "github.com/superseriousbusiness/gotosocial/internal/web" 48 "github.com/superseriousbusiness/gotosocial/testrig" 49 ) 50 51 // Start creates and starts a gotosocial testrig server 52 var Start action.GTSAction = func(ctx context.Context) error { 53 var state state.State 54 55 testrig.InitTestConfig() 56 testrig.InitTestLog() 57 58 if err := tracing.Initialize(); err != nil { 59 return fmt.Errorf("error initializing tracing: %w", err) 60 } 61 62 // Initialize caches and database 63 state.DB = testrig.NewTestDB(&state) 64 65 // New test db inits caches so we don't need to do 66 // that twice, we can just start the initialized caches. 67 state.Caches.Start() 68 defer state.Caches.Stop() 69 70 testrig.StandardDBSetup(state.DB, nil) 71 72 if os.Getenv("GTS_STORAGE_BACKEND") == "s3" { 73 state.Storage, _ = storage.NewS3Storage() 74 } else { 75 state.Storage = testrig.NewInMemoryStorage() 76 } 77 testrig.StandardStorageSetup(state.Storage, "./testrig/media") 78 79 // Initialize workers. 80 state.Workers.Start() 81 defer state.Workers.Stop() 82 83 // build backend handlers 84 transportController := testrig.NewTestTransportController(&state, testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) { 85 r := io.NopCloser(bytes.NewReader([]byte{})) 86 return &http.Response{ 87 StatusCode: 200, 88 Body: r, 89 }, nil 90 }, "")) 91 mediaManager := testrig.NewTestMediaManager(&state) 92 federator := testrig.NewTestFederator(&state, transportController, mediaManager) 93 94 emailSender := testrig.NewEmailSender("./web/template/", nil) 95 typeConverter := testrig.NewTestTypeConverter(state.DB) 96 filter := visibility.NewFilter(&state) 97 98 // Initialize timelines. 99 state.Timelines.Home = timeline.NewManager( 100 tlprocessor.HomeTimelineGrab(&state), 101 tlprocessor.HomeTimelineFilter(&state, filter), 102 tlprocessor.HomeTimelineStatusPrepare(&state, typeConverter), 103 tlprocessor.SkipInsert(), 104 ) 105 if err := state.Timelines.Home.Start(); err != nil { 106 return fmt.Errorf("error starting home timeline: %s", err) 107 } 108 109 state.Timelines.List = timeline.NewManager( 110 tlprocessor.ListTimelineGrab(&state), 111 tlprocessor.ListTimelineFilter(&state, filter), 112 tlprocessor.ListTimelineStatusPrepare(&state, typeConverter), 113 tlprocessor.SkipInsert(), 114 ) 115 if err := state.Timelines.List.Start(); err != nil { 116 return fmt.Errorf("error starting list timeline: %s", err) 117 } 118 119 processor := testrig.NewTestProcessor(&state, federator, emailSender, mediaManager) 120 121 /* 122 HTTP router initialization 123 */ 124 125 router := testrig.NewTestRouter(state.DB) 126 middlewares := []gin.HandlerFunc{ 127 middleware.AddRequestID(config.GetRequestIDHeader()), // requestID middleware must run before tracing 128 } 129 if config.GetTracingEnabled() { 130 middlewares = append(middlewares, tracing.InstrumentGin()) 131 } 132 middlewares = append(middlewares, []gin.HandlerFunc{ 133 middleware.Logger(config.GetLogClientIP()), 134 middleware.UserAgent(), 135 middleware.CORS(), 136 middleware.ExtraHeaders(), 137 }...) 138 139 // attach global middlewares which are used for every request 140 router.AttachGlobalMiddleware(middlewares...) 141 142 // attach global no route / 404 handler to the router 143 router.AttachNoRouteHandler(func(c *gin.Context) { 144 apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(errors.New(http.StatusText(http.StatusNotFound))), processor.InstanceGetV1) 145 }) 146 147 // build router modules 148 var idp oidc.IDP 149 var err error 150 if config.GetOIDCEnabled() { 151 idp, err = oidc.NewIDP(ctx) 152 if err != nil { 153 return fmt.Errorf("error creating oidc idp: %w", err) 154 } 155 } 156 157 routerSession, err := state.DB.GetSession(ctx) 158 if err != nil { 159 return fmt.Errorf("error retrieving router session for session middleware: %w", err) 160 } 161 162 sessionName, err := middleware.SessionName() 163 if err != nil { 164 return fmt.Errorf("error generating session name for session middleware: %w", err) 165 } 166 167 var ( 168 authModule = api.NewAuth(state.DB, processor, idp, routerSession, sessionName) // auth/oauth paths 169 clientModule = api.NewClient(state.DB, processor) // api client endpoints 170 fileserverModule = api.NewFileserver(processor) // fileserver endpoints 171 wellKnownModule = api.NewWellKnown(processor) // .well-known endpoints 172 nodeInfoModule = api.NewNodeInfo(processor) // nodeinfo endpoint 173 activityPubModule = api.NewActivityPub(state.DB, processor) // ActivityPub endpoints 174 webModule = web.New(state.DB, processor) // web pages + user profiles + settings panels etc 175 ) 176 177 // these should be routed in order 178 authModule.Route(router) 179 clientModule.Route(router) 180 fileserverModule.Route(router) 181 wellKnownModule.Route(router) 182 nodeInfoModule.Route(router) 183 activityPubModule.Route(router) 184 activityPubModule.RoutePublicKey(router) 185 webModule.Route(router) 186 187 gts, err := gotosocial.NewServer(state.DB, router, federator, mediaManager) 188 if err != nil { 189 return fmt.Errorf("error creating gotosocial service: %s", err) 190 } 191 192 if err := gts.Start(ctx); err != nil { 193 return fmt.Errorf("error starting gotosocial service: %s", err) 194 } 195 196 // catch shutdown signals from the operating system 197 sigs := make(chan os.Signal, 1) 198 signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) 199 sig := <-sigs 200 log.Infof(ctx, "received signal %s, shutting down", sig) 201 202 testrig.StandardDBTeardown(state.DB) 203 testrig.StandardStorageTeardown(state.Storage) 204 205 // close down all running services in order 206 if err := gts.Stop(ctx); err != nil { 207 return fmt.Errorf("error closing gotosocial service: %s", err) 208 } 209 210 log.Info(ctx, "done! exiting...") 211 return nil 212 }