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  }