github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/it/impl_test.go (about) 1 /* 2 * Copyright (c) 2022-present unTill Pro, Ltd. 3 */ 4 5 package sys_it 6 7 import ( 8 "encoding/base64" 9 "fmt" 10 "sync" 11 "testing" 12 13 "github.com/stretchr/testify/require" 14 15 "github.com/voedger/voedger/pkg/appdef" 16 "github.com/voedger/voedger/pkg/istructs" 17 "github.com/voedger/voedger/pkg/state" 18 coreutils "github.com/voedger/voedger/pkg/utils" 19 it "github.com/voedger/voedger/pkg/vit" 20 ) 21 22 func TestAuthorization(t *testing.T) { 23 vit := it.NewVIT(t, &it.SharedConfig_App1) 24 defer vit.TearDown() 25 26 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 27 prn := ws.Owner 28 29 body := `{"cuds":[{"fields":{"sys.ID": 1,"sys.QName":"app1pkg.air_table_plan"}}]}` 30 31 t.Run("basic usage", func(t *testing.T) { 32 t.Run("Bearer scheme", func(t *testing.T) { 33 sys := vit.GetSystemPrincipal(istructs.AppQName_test1_app1) 34 vit.PostWS(ws, "c.sys.CUD", body, coreutils.WithHeaders(coreutils.Authorization, "Bearer "+sys.Token)) 35 }) 36 37 t.Run("Basic scheme", func(t *testing.T) { 38 t.Run("token in username only", func(t *testing.T) { 39 basicAuthHeader := base64.StdEncoding.EncodeToString([]byte(prn.Token + ":")) 40 vit.PostWS(ws, "c.sys.CUD", body, coreutils.WithHeaders(coreutils.Authorization, "Basic "+basicAuthHeader)) 41 }) 42 t.Run("token in password only", func(t *testing.T) { 43 basicAuthHeader := base64.StdEncoding.EncodeToString([]byte(":" + prn.Token)) 44 vit.PostWS(ws, "c.sys.CUD", body, coreutils.WithHeaders(coreutils.Authorization, "Basic "+basicAuthHeader)) 45 }) 46 t.Run("token is splitted over username and password", func(t *testing.T) { 47 basicAuthHeader := base64.StdEncoding.EncodeToString([]byte(prn.Token[:len(prn.Token)/2] + ":" + prn.Token[len(prn.Token)/2:])) 48 vit.PostWS(ws, "c.sys.CUD", body, coreutils.WithHeaders(coreutils.Authorization, "Basic "+basicAuthHeader)) 49 }) 50 }) 51 }) 52 53 t.Run("401 unauthorized", func(t *testing.T) { 54 t.Run("wrong Authorization token", func(t *testing.T) { 55 vit.PostProfile(prn, "c.sys.CUD", body, coreutils.WithAuthorizeBy("wrong"), coreutils.Expect401()).Println() 56 }) 57 58 t.Run("unsupported Authorization header", func(t *testing.T) { 59 vit.PostProfile(prn, "c.sys.CUD", body, 60 coreutils.WithHeaders(coreutils.Authorization, `whatever w\o Bearer or Basic`), coreutils.Expect401()).Println() 61 }) 62 63 t.Run("Basic authorization", func(t *testing.T) { 64 t.Run("non-base64 header value", func(t *testing.T) { 65 vit.PostProfile(prn, "c.sys.CUD", body, 66 coreutils.WithHeaders(coreutils.Authorization, `Basic non-base64-value`), coreutils.Expect401()).Println() 67 }) 68 t.Run("no colon between username and password", func(t *testing.T) { 69 headerValue := base64.RawStdEncoding.EncodeToString([]byte("some password")) 70 vit.PostProfile(prn, "c.sys.CUD", body, 71 coreutils.WithHeaders(coreutils.Authorization, "Basic "+headerValue), coreutils.Expect401()).Println() 72 }) 73 }) 74 }) 75 76 t.Run("403 forbidden", func(t *testing.T) { 77 t.Run("missing Authorization token", func(t *testing.T) { 78 // c.sys.CUD has Owner authorization policy -> need to provide authorization header in PostFunc() 79 vit.PostApp(istructs.AppQName_test1_app1, prn.ProfileWSID, "c.sys.CUD", body, coreutils.Expect403()).Println() 80 }) 81 }) 82 } 83 84 func TestUtilFuncs(t *testing.T) { 85 require := require.New(t) 86 vit := it.NewVIT(t, &it.SharedConfig_App1) 87 defer vit.TearDown() 88 89 t.Run("func Echo", func(t *testing.T) { 90 body := `{"args": {"Text": "world"},"elements":[{"fields":["Res"]}]}` 91 resp := vit.PostApp(istructs.AppQName_test1_app1, 1, "q.sys.Echo", body) 92 require.Equal("world", resp.SectionRow()[0].(string)) 93 resp.Println() 94 }) 95 96 t.Run("func GRCount", func(t *testing.T) { 97 body := `{"args": {},"elements":[{"fields":["NumGoroutines"]}]}` 98 resp := vit.PostApp(istructs.AppQName_test1_app1, 1, "q.sys.GRCount", body) 99 resp.Println() 100 }) 101 102 t.Run("func Modules", func(t *testing.T) { 103 // should normally return nothing because there is no dpes information in tests 104 // returns actual deps if the Voedger is used in some main() and built using `go build` 105 body := `{"args": {},"elements":[{"fields":["Modules"]}]}` 106 resp := vit.PostApp(istructs.AppQName_test1_app1, 1, "q.sys.Modules", body) 107 resp.Println() 108 }) 109 } 110 111 func Test400BadRequests(t *testing.T) { 112 vit := it.NewVIT(t, &it.SharedConfig_App1) 113 defer vit.TearDown() 114 var err error 115 116 cases := []struct { 117 desc string 118 funcName string 119 appName string 120 }{ 121 {desc: "unknown func", funcName: "q.unknown"}, 122 {desc: "unknown func kind", funcName: "x.test.test"}, 123 {desc: "wrong resource name", funcName: "a"}, 124 {desc: "unknown app", funcName: "c.sys.CUD", appName: "un/known"}, 125 } 126 127 for _, c := range cases { 128 t.Run(c.desc, func(t *testing.T) { 129 appQName := istructs.AppQName_test1_app1 130 if len(c.appName) > 0 { 131 appQName, err = istructs.ParseAppQName(c.appName) 132 require.NoError(t, err) 133 } 134 vit.PostApp(appQName, 1, c.funcName, "", coreutils.Expect400()).Println() 135 }) 136 } 137 } 138 139 func Test503OnNoQueryProcessorsAvailable(t *testing.T) { 140 funcStarted := make(chan interface{}) 141 okToFinish := make(chan interface{}) 142 it.MockQryExec = func(input string, callback istructs.ExecQueryCallback) error { 143 funcStarted <- nil 144 <-okToFinish 145 return nil 146 } 147 vit := it.NewVIT(t, &it.SharedConfig_App1) 148 defer vit.TearDown() 149 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 150 body := `{"args": {"Input": "world"},"elements": [{"fields": ["Res"]}]}` 151 postDone := sync.WaitGroup{} 152 sys := vit.GetSystemPrincipal(istructs.AppQName_test1_app1) 153 for i := 0; i < int(vit.VVMConfig.NumCommandProcessors); i++ { 154 postDone.Add(1) 155 go func() { 156 defer postDone.Done() 157 vit.PostWS(ws, "q.app1pkg.MockQry", body, coreutils.WithAuthorizeBy(sys.Token)) 158 }() 159 160 <-funcStarted 161 } 162 163 // one more request to any WSID -> 503 service unavailable 164 vit.PostApp(istructs.AppQName_test1_app1, 1, "q.sys.Echo", body, coreutils.Expect503(), coreutils.WithAuthorizeBy(sys.Token)) 165 166 for i := 0; i < int(vit.VVMConfig.NumQueryProcessors); i++ { 167 okToFinish <- nil 168 } 169 postDone.Wait() 170 } 171 172 func TestCmdResult(t *testing.T) { 173 require := require.New(t) 174 vit := it.NewVIT(t, &it.SharedConfig_App1) 175 defer vit.TearDown() 176 177 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 178 179 t.Run("basic usage", func(t *testing.T) { 180 181 body := `{"args":{"Arg1": 1}}` 182 resp := vit.PostWS(ws, "c.app1pkg.TestCmd", body) 183 resp.Println() 184 require.Equal("Str", resp.CmdResult["Str"]) 185 require.Equal(float64(42), resp.CmdResult["Int"]) 186 }) 187 188 // ok - just required field is filled 189 t.Run("just required fields filled", func(t *testing.T) { 190 body := fmt.Sprintf(`{"args":{"Arg1":%d}}`, 2) 191 resp := vit.PostWS(ws, "c.app1pkg.TestCmd", body) 192 resp.Println() 193 require.Equal(float64(42), resp.CmdResult["Int"]) 194 }) 195 196 t.Run("missing required fields -> 500", func(t *testing.T) { 197 body := fmt.Sprintf(`{"args":{"Arg1":%d}}`, 3) 198 vit.PostWS(ws, "c.app1pkg.TestCmd", body, coreutils.Expect500()).Println() 199 }) 200 201 t.Run("wrong types -> 500", func(t *testing.T) { 202 body := fmt.Sprintf(`{"args":{"Arg1":%d}}`, 4) 203 vit.PostWS(ws, "c.app1pkg.TestCmd", body, coreutils.Expect500()).Println() 204 }) 205 } 206 207 func TestIsActiveValidation(t *testing.T) { 208 vit := it.NewVIT(t, &it.SharedConfig_App1) 209 defer vit.TearDown() 210 211 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 212 213 t.Run("deny update sys.IsActive and other fields", func(t *testing.T) { 214 body := `{"cuds":[{"fields":{"sys.ID": 1,"sys.QName":"app1pkg.air_table_plan"}}]}` 215 id := vit.PostWS(ws, "c.sys.CUD", body).NewID() 216 body = fmt.Sprintf(`{"cuds":[{"sys.ID":%d,"fields":{"sys.IsActive":false,"name":"newName"}}]}`, id) 217 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect403()).Println() 218 }) 219 220 t.Run("deny insert a deactivated record", func(t *testing.T) { 221 body := `{"cuds":[{"fields":{"sys.ID": 1,"sys.QName":"app1pkg.air_table_plan","sys.IsActive":false}}]}` 222 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect403()).Println() 223 }) 224 } 225 226 func TestTakeQNamesFromWorkspace(t *testing.T) { 227 vit := it.NewVIT(t, &it.SharedConfig_App1) 228 defer vit.TearDown() 229 230 t.Run("command", func(t *testing.T) { 231 t.Run("existence", func(t *testing.T) { 232 233 anotherWS := vit.WS(istructs.AppQName_test1_app1, "test_ws_another") 234 body := fmt.Sprintf(`{"args":{"Arg1":%d}}`, 1) 235 // c.app1pkg.TestCmd is not defined in test_ws_anotherWS workspace -> 400 bad request 236 vit.PostWS(anotherWS, "c.app1pkg.TestCmd", body, coreutils.Expect400("command app1pkg.TestCmd does not exist in workspace app1pkg.test_wsWS_another")) 237 238 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 239 body = "{}" 240 // c.app1pkg.testCmd is defined in test_wsWS workspace -> 400 bad request 241 vit.PostWS(ws, "c.app1pkg.testCmd", body, coreutils.Expect400("command app1pkg.testCmd does not exist in workspace app1pkg.test_wsWS")) 242 }) 243 244 t.Run("type", func(t *testing.T) { 245 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 246 body := "{}" 247 // c.app1pkg.testCmd is defined in test_wsWS workspace -> 400 bad request 248 vit.PostWS(ws, "c.app1pkg.MockQry", body, coreutils.Expect400("app1pkg.MockQry is not a command")) 249 }) 250 }) 251 252 t.Run("query", func(t *testing.T) { 253 t.Run("existensce", func(t *testing.T) { 254 anotherWS := vit.WS(istructs.AppQName_test1_app1, "test_ws_another") 255 body := `{"args":{"Input":"str"}}` 256 // q.app1pkg.MockQry is not defined in test_ws_anotherWS workspace -> 400 bad request 257 vit.PostWS(anotherWS, "q.app1pkg.MockQry", body, coreutils.Expect400("query app1pkg.MockQry does not exist in workspace app1pkg.test_wsWS_another")) 258 }) 259 t.Run("type", func(t *testing.T) { 260 anotherWS := vit.WS(istructs.AppQName_test1_app1, "test_ws_another") 261 body := fmt.Sprintf(`{"args":{"Arg1":%d}}`, 1) 262 vit.PostWS(anotherWS, "q.app1pkg.testCmd", body, coreutils.Expect400("app1pkg.testCmd is not a query")) 263 }) 264 }) 265 266 t.Run("CUDs QNames", func(t *testing.T) { 267 t.Run("CUD in the request", func(t *testing.T) { 268 anotherWS := vit.WS(istructs.AppQName_test1_app1, "test_ws_another") 269 body := `{"cuds":[{"fields":{"sys.ID": 1,"sys.QName":"app1pkg.options"}}]}` 270 // try to insert a QName that does not exist in the workspace -> 403 foribidden ??? or 400 bad request ??? 271 vit.PostWS(anotherWS, "c.sys.CUD", body, coreutils.Expect400("app1pkg.options", "does not exist in the workspace app1pkg.test_ws_another")) 272 }) 273 t.Run("CUD produced by a command", func(t *testing.T) { 274 it.MockCmdExec = func(input string, args istructs.ExecCommandArgs) error { 275 kb, err := args.State.KeyBuilder(state.Record, appdef.NewQName("app1pkg", "docInAnotherWS")) 276 if err != nil { 277 return err 278 } 279 vb, err := args.Intents.NewValue(kb) 280 if err != nil { 281 return err 282 } 283 vb.PutRecordID(appdef.SystemField_ID, 1) 284 return nil 285 } 286 body := `{"args":{"Input":"Str"}}` 287 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 288 vit.PostWS(ws, "c.app1pkg.MockCmd", body, coreutils.WithExpectedCode(500, "app1pkg.docInAnotherWS is not available in workspace with descriptor app1pkg.test_ws")) 289 }) 290 }) 291 } 292 293 func TestVITResetPreservingStorage(t *testing.T) { 294 cfg := it.NewOwnVITConfig( 295 it.WithApp(istructs.AppQName_test1_app1, it.ProvideApp1, 296 it.WithUserLogin("login", "1"), 297 it.WithChildWorkspace(it.QNameApp1_TestWSKind, "test_ws", "", "", "login", map[string]interface{}{"IntFld": 42}), 298 ), 299 ) 300 categoryID := int64(0) 301 it.TestRestartPreservingStorage(t, &cfg, func(t *testing.T, vit *it.VIT) { 302 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 303 body := `{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.category","name":"Awesome food"}}]}` 304 categoryID = vit.PostWS(ws, "c.sys.CUD", body).NewID() 305 }, func(t *testing.T, vit *it.VIT) { 306 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 307 body := fmt.Sprintf(`{"args":{"Query":"select * from app1pkg.category where id = %d"},"elements":[{"fields":["Result"]}]}`, categoryID) 308 resp := vit.PostWS(ws, "q.sys.SqlQuery", body) 309 require.Contains(t, resp.SectionRow()[0].(string), `"name":"Awesome food"`) 310 resp.Println() 311 }) 312 } 313 314 func TestAdminEndpoint(t *testing.T) { 315 require := require.New(t) 316 vit := it.NewVIT(t, &it.SharedConfig_App1) 317 defer vit.TearDown() 318 body := `{"args": {"Text": "world"},"elements":[{"fields":["Res"]}]}` 319 resp, err := vit.IFederation.AdminFunc(fmt.Sprintf("api/%s/1/q.sys.Echo", istructs.AppQName_test1_app1), body) 320 require.NoError(err) 321 require.Equal("world", resp.SectionRow()[0].(string)) 322 resp.Println() 323 }