commit
4cd47c55b1
13 changed files with 240 additions and 15 deletions
|
|
@ -1 +0,0 @@
|
||||||
package cmd
|
|
||||||
18
cmd/root.go
18
cmd/root.go
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/yaoapp/kun/exception"
|
"github.com/yaoapp/kun/exception"
|
||||||
|
"github.com/yaoapp/yao/cmd/studio"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
"github.com/yaoapp/yao/share"
|
"github.com/yaoapp/yao/share"
|
||||||
)
|
)
|
||||||
|
|
@ -84,8 +85,23 @@ var rootCmd = &cobra.Command{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var studioCmd = &cobra.Command{
|
||||||
|
Use: "studio",
|
||||||
|
Short: "Yao Studio CLI",
|
||||||
|
Long: `Yao Studio CLI`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
CompletionOptions: cobra.CompletionOptions{
|
||||||
|
DisableDefaultCmd: true,
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Fprintln(os.Stderr, L("One or more arguments are not correct"), args)
|
||||||
|
os.Exit(1)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// 加载命令
|
// 加载命令
|
||||||
func init() {
|
func init() {
|
||||||
|
studioCmd.AddCommand(studio.RunCmd)
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
versionCmd,
|
versionCmd,
|
||||||
migrateCmd,
|
migrateCmd,
|
||||||
|
|
@ -93,11 +109,11 @@ func init() {
|
||||||
startCmd,
|
startCmd,
|
||||||
runCmd,
|
runCmd,
|
||||||
initCmd,
|
initCmd,
|
||||||
serviceCmd,
|
|
||||||
dumpCmd,
|
dumpCmd,
|
||||||
restoreCmd,
|
restoreCmd,
|
||||||
socketCmd,
|
socketCmd,
|
||||||
websocketCmd,
|
websocketCmd,
|
||||||
|
studioCmd,
|
||||||
)
|
)
|
||||||
// rootCmd.SetHelpCommand(helpCmd)
|
// rootCmd.SetHelpCommand(helpCmd)
|
||||||
rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory"))
|
rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory"))
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/yaoapp/yao/share"
|
"github.com/yaoapp/yao/share"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// **** DEPRECATED ****
|
||||||
var serviceCmd = &cobra.Command{
|
var serviceCmd = &cobra.Command{
|
||||||
Use: "service",
|
Use: "service",
|
||||||
Short: L("Service manager"),
|
Short: L("Service manager"),
|
||||||
|
|
|
||||||
31
cmd/start.go
31
cmd/start.go
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/yaoapp/yao/fs"
|
"github.com/yaoapp/yao/fs"
|
||||||
"github.com/yaoapp/yao/service"
|
"github.com/yaoapp/yao/service"
|
||||||
"github.com/yaoapp/yao/share"
|
"github.com/yaoapp/yao/share"
|
||||||
|
"github.com/yaoapp/yao/studio"
|
||||||
)
|
)
|
||||||
|
|
||||||
var startDebug = false
|
var startDebug = false
|
||||||
|
|
@ -90,11 +91,18 @@ var startCmd = &cobra.Command{
|
||||||
|
|
||||||
// development mode
|
// development mode
|
||||||
if mode == "development" {
|
if mode == "development" {
|
||||||
|
|
||||||
|
// Start Studio Server
|
||||||
|
go studio.Start(config.Conf)
|
||||||
|
defer studio.Stop()
|
||||||
|
|
||||||
printApis(false)
|
printApis(false)
|
||||||
printTasks(false)
|
printTasks(false)
|
||||||
printSchedules(false)
|
printSchedules(false)
|
||||||
printConnectors(false)
|
printConnectors(false)
|
||||||
printStores(false)
|
printStores(false)
|
||||||
|
printStudio(false, host)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode == "development" && !startDisableWatching {
|
if mode == "development" && !startDisableWatching {
|
||||||
|
|
@ -185,6 +193,29 @@ func printStores(silent bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printStudio(silent bool, host string) {
|
||||||
|
|
||||||
|
if silent {
|
||||||
|
log.Info("[Studio] http://%s:%d", host, config.Conf.Studio.Port)
|
||||||
|
if config.Conf.Studio.Auto {
|
||||||
|
log.Info("[Studio] Secret: %s", config.Conf.Studio.Secret)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(color.WhiteString("\n---------------------------------"))
|
||||||
|
fmt.Println(color.WhiteString(L("Yao Studio Server")))
|
||||||
|
fmt.Println(color.WhiteString("---------------------------------"))
|
||||||
|
fmt.Printf(color.CyanString("HOST : "))
|
||||||
|
fmt.Printf(color.WhiteString(" %s\n", config.Conf.Host))
|
||||||
|
fmt.Printf(color.CyanString("PORT : "))
|
||||||
|
fmt.Printf(color.WhiteString(" %d\n", config.Conf.Studio.Port))
|
||||||
|
if config.Conf.Studio.Auto {
|
||||||
|
fmt.Printf(color.CyanString("SECRET: "))
|
||||||
|
fmt.Printf(color.WhiteString(" %s\n", config.Conf.Studio.Secret))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func printSchedules(silent bool) {
|
func printSchedules(silent bool) {
|
||||||
|
|
||||||
if len(gou.Schedules) == 0 {
|
if len(gou.Schedules) == 0 {
|
||||||
|
|
|
||||||
93
cmd/studio/run.go
Normal file
93
cmd/studio/run.go
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
package studio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/kun/exception"
|
||||||
|
"github.com/yaoapp/kun/utils"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/engine"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunCmd command
|
||||||
|
var RunCmd = &cobra.Command{
|
||||||
|
Use: "run",
|
||||||
|
Short: L("Execute Yao Studio Script"),
|
||||||
|
Long: L("Execute Yao Studio Script"),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
defer share.SessionStop()
|
||||||
|
defer gou.KillPlugins()
|
||||||
|
defer func() {
|
||||||
|
err := exception.Catch(recover())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(color.RedString(L("Fatal: %s"), err.Error()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
Boot()
|
||||||
|
cfg := config.Conf
|
||||||
|
cfg.Session.IsCLI = true
|
||||||
|
engine.Load(cfg)
|
||||||
|
if len(args) < 1 {
|
||||||
|
fmt.Println(color.RedString(L("Not enough arguments")))
|
||||||
|
fmt.Println(color.WhiteString(share.BUILDNAME + " help"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := strings.Split(args[0], ".")
|
||||||
|
service := strings.Join(name[0:len(name)-1], ".")
|
||||||
|
method := name[len(name)-1]
|
||||||
|
|
||||||
|
fmt.Println(color.GreenString(L("Studio Run: %s"), args[0]))
|
||||||
|
pargs := []interface{}{}
|
||||||
|
for i, arg := range args {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(arg, "::") {
|
||||||
|
arg := strings.TrimPrefix(arg, "::")
|
||||||
|
var v interface{}
|
||||||
|
err := jsoniter.Unmarshal([]byte(arg), &v)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(color.RedString(L("Arguments: %s"), err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pargs = append(pargs, v)
|
||||||
|
fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg))
|
||||||
|
} else if strings.HasPrefix(arg, "\\::") {
|
||||||
|
arg := "::" + strings.TrimPrefix(arg, "\\::")
|
||||||
|
pargs = append(pargs, arg)
|
||||||
|
fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg))
|
||||||
|
} else {
|
||||||
|
pargs = append(pargs, arg)
|
||||||
|
fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req := gou.Yao.New(service, method)
|
||||||
|
res, err := req.RootCall(pargs...)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(color.RedString("--------------------------------------"))
|
||||||
|
fmt.Println(color.RedString(L("%s Error"), args[0]))
|
||||||
|
fmt.Println(color.RedString("--------------------------------------"))
|
||||||
|
utils.Dump(err)
|
||||||
|
fmt.Println(color.RedString("--------------------------------------"))
|
||||||
|
fmt.Println(color.GreenString(L("✨DONE✨")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(color.WhiteString("--------------------------------------"))
|
||||||
|
fmt.Println(color.WhiteString(L("%s Response"), args[0]))
|
||||||
|
fmt.Println(color.WhiteString("--------------------------------------"))
|
||||||
|
utils.Dump(res)
|
||||||
|
fmt.Println(color.WhiteString("--------------------------------------"))
|
||||||
|
fmt.Println(color.GreenString(L("✨DONE✨")))
|
||||||
|
},
|
||||||
|
}
|
||||||
53
cmd/studio/utils.go
Normal file
53
cmd/studio/utils.go
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package studio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/yaoapp/kun/exception"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var appPath string
|
||||||
|
var envFile string
|
||||||
|
|
||||||
|
var langs = map[string]string{
|
||||||
|
"Start Engine": "启动象传应用引擎",
|
||||||
|
}
|
||||||
|
|
||||||
|
// L 多语言切换
|
||||||
|
func L(words string) string {
|
||||||
|
|
||||||
|
var lang = os.Getenv("YAO_LANG")
|
||||||
|
if lang == "" {
|
||||||
|
return words
|
||||||
|
}
|
||||||
|
|
||||||
|
if trans, has := langs[words]; has {
|
||||||
|
return trans
|
||||||
|
}
|
||||||
|
return words
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boot 设定配置
|
||||||
|
func Boot() {
|
||||||
|
root := config.Conf.Root
|
||||||
|
if appPath != "" {
|
||||||
|
r, err := filepath.Abs(appPath)
|
||||||
|
if err != nil {
|
||||||
|
exception.New("Root error %s", 500, err.Error()).Throw()
|
||||||
|
}
|
||||||
|
root = r
|
||||||
|
}
|
||||||
|
if envFile != "" {
|
||||||
|
config.Conf = config.LoadFrom(envFile)
|
||||||
|
} else {
|
||||||
|
config.Conf = config.LoadFrom(filepath.Join(root, ".env"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Conf.Mode == "production" {
|
||||||
|
config.Production()
|
||||||
|
} else if config.Conf.Mode == "development" {
|
||||||
|
config.Development()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/yaoapp/yao/share"
|
"github.com/yaoapp/yao/share"
|
||||||
"github.com/yaoapp/yao/socket"
|
"github.com/yaoapp/yao/socket"
|
||||||
"github.com/yaoapp/yao/store"
|
"github.com/yaoapp/yao/store"
|
||||||
|
"github.com/yaoapp/yao/studio"
|
||||||
"github.com/yaoapp/yao/table"
|
"github.com/yaoapp/yao/table"
|
||||||
"github.com/yaoapp/yao/task"
|
"github.com/yaoapp/yao/task"
|
||||||
"github.com/yaoapp/yao/websocket"
|
"github.com/yaoapp/yao/websocket"
|
||||||
|
|
@ -64,6 +65,12 @@ func Load(cfg config.Config) (err error) {
|
||||||
printErr(cfg.Mode, "FileSystem", err)
|
printErr(cfg.Mode, "FileSystem", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load Studio
|
||||||
|
err = studio.Load(cfg)
|
||||||
|
if err != nil {
|
||||||
|
printErr(cfg.Mode, "Studio", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 第二步: 建立数据库 & 会话连接
|
// 第二步: 建立数据库 & 会话连接
|
||||||
share.DBConnect(cfg.DB) // 创建数据库连接
|
share.DBConnect(cfg.DB) // 创建数据库连接
|
||||||
// share.SessionConnect(cfg.Session) // 创建会话服务器链接
|
// share.SessionConnect(cfg.Session) // 创建会话服务器链接
|
||||||
|
|
|
||||||
6
fs/fs.go
6
fs/fs.go
|
|
@ -34,9 +34,9 @@ func Load(cfg config.Config) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.Register("system", system.New(dataRoot))
|
fs.Register("system", system.New(dataRoot))
|
||||||
fs.Register("dsl", dsl.New(root).DenyAbs(dslDenyList...)) // DSL
|
// fs.Register("binary", system.New(root)) // Next
|
||||||
fs.Register("script", system.New(scriptRoot)) // Script
|
fs.RootRegister("dsl", dsl.New(root).DenyAbs(dslDenyList...)) // DSL
|
||||||
fs.Register("binary", system.New(root)) // Binary
|
fs.RootRegister("script", system.New(scriptRoot)) // Script
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ func TestLoad(t *testing.T) {
|
||||||
|
|
||||||
func TestDSL(t *testing.T) {
|
func TestDSL(t *testing.T) {
|
||||||
Load(config.Conf)
|
Load(config.Conf)
|
||||||
dsl := fs.MustGet("dsl")
|
dsl := fs.MustRootGet("dsl")
|
||||||
name := filepath.Join("models", "test.mod.json")
|
name := filepath.Join("models", "test.mod.json")
|
||||||
_, err := fs.WriteFile(dsl, name, []byte(`{"foo": "bar", "hello":{ "int": 1, "float": 0.618}}`), 0644)
|
_, err := fs.WriteFile(dsl, name, []byte(`{"foo": "bar", "hello":{ "int": 1, "float": 0.618}}`), 0644)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
@ -45,11 +45,16 @@ func TestDSL(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = fs.Get("dsl")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "dsl does not registered")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScirpt(t *testing.T) {
|
func TestScirpt(t *testing.T) {
|
||||||
Load(config.Conf)
|
Load(config.Conf)
|
||||||
script := fs.MustGet("script")
|
script := fs.MustRootGet("script")
|
||||||
name := "test.js"
|
name := "test.js"
|
||||||
_, err := fs.WriteFile(script, name, []byte(`console.log("hello")`), 0644)
|
_, err := fs.WriteFile(script, name, []byte(`console.log("hello")`), 0644)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
@ -61,4 +66,8 @@ func TestScirpt(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = fs.Get("script")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "script does not registered")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,18 @@ func hdRecovered(c *gin.Context, recovered interface{}) {
|
||||||
c.AbortWithStatus(code)
|
c.AbortWithStatus(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cross domian
|
// CORS Cross-origin
|
||||||
func hdCrossDomain(c *gin.Context) {
|
func hdCORS(c *gin.Context) {
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||||
|
|
||||||
|
if c.Request.Method == "OPTIONS" {
|
||||||
|
c.AbortWithStatus(204)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// studio API Auth
|
// studio API Auth
|
||||||
|
|
@ -63,6 +73,7 @@ func hdAuth(c *gin.Context) {
|
||||||
|
|
||||||
claims := helper.JwtValidate(tokenString, config.Conf.Studio.Secret)
|
claims := helper.JwtValidate(tokenString, config.Conf.Studio.Secret)
|
||||||
c.Set("__sid", claims.SID)
|
c.Set("__sid", claims.SID)
|
||||||
|
c.Next()
|
||||||
return
|
return
|
||||||
|
|
||||||
} else if strings.HasPrefix(tokenString, "Signature ") { // For Yao Studio
|
} else if strings.HasPrefix(tokenString, "Signature ") { // For Yao Studio
|
||||||
|
|
@ -71,6 +82,7 @@ func hdAuth(c *gin.Context) {
|
||||||
ts := c.Request.Header.Get("Studio-Timestamp")
|
ts := c.Request.Header.Get("Studio-Timestamp")
|
||||||
query := c.Request.URL.Query()
|
query := c.Request.URL.Query()
|
||||||
log.Trace("[Studio] %s, %s %s %v", signature, nonce, ts, query)
|
log.Trace("[Studio] %s, %s %s %v", signature, nonce, ts, query)
|
||||||
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ var regExcp = regexp.MustCompile("^Exception\\|([0-9]+):(.+)$")
|
||||||
// Serve start the api server
|
// Serve start the api server
|
||||||
func setRouter(router *gin.Engine) {
|
func setRouter(router *gin.Engine) {
|
||||||
|
|
||||||
router.Use(gin.CustomRecovery(hdRecovered), hdAuth)
|
router.Use(gin.CustomRecovery(hdRecovered), hdCORS, hdAuth)
|
||||||
|
|
||||||
// DSL ReadDir, ReadFile
|
// DSL ReadDir, ReadFile
|
||||||
router.GET("/dsl/:method", func(c *gin.Context) {
|
router.GET("/dsl/:method", func(c *gin.Context) {
|
||||||
|
|
@ -182,7 +182,7 @@ func setRouter(router *gin.Engine) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
service := fmt.Sprintf("__yao.studio.%s", c.Param("name"))
|
service := c.Param("name")
|
||||||
|
|
||||||
payload, err := io.ReadAll(c.Request.Body)
|
payload, err := io.ReadAll(c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -207,7 +207,7 @@ func setRouter(router *gin.Engine) {
|
||||||
req.WithSid(fmt.Sprintf("%s", sid))
|
req.WithSid(fmt.Sprintf("%s", sid))
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := req.Call(fun.Args...)
|
res, err := req.RootCall(fun.Args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// parse Exception
|
// parse Exception
|
||||||
code := 500
|
code := 500
|
||||||
|
|
|
||||||
|
|
@ -134,8 +134,8 @@ func loadScriptFrom(dir string) error {
|
||||||
|
|
||||||
messages := []string{}
|
messages := []string{}
|
||||||
err := share.Walk(dir, ".js", func(root, filename string) {
|
err := share.Walk(dir, ".js", func(root, filename string) {
|
||||||
name := fmt.Sprintf("__yao.studio.%s", share.SpecName(root, filename))
|
name := share.SpecName(root, filename)
|
||||||
err := gou.Yao.Load(filename, name)
|
err := gou.Yao.RootLoad(filename, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
messages = append(messages, err.Error())
|
messages = append(messages, err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,13 @@ func TestLoad(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.NotNil(t, dfs)
|
assert.NotNil(t, dfs)
|
||||||
|
|
||||||
res, err := gou.Yao.Engine.Call(map[string]interface{}{}, "__yao.studio.table", "Ping")
|
res, err := gou.Yao.Engine.RootCall(map[string]interface{}{}, "table", "Ping")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "PONG", res)
|
assert.Equal(t, "PONG", res)
|
||||||
|
|
||||||
|
_, err = gou.Yao.Engine.Call(map[string]interface{}{}, "table", "Ping")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "The table does not loaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStartStop(t *testing.T) {
|
func TestStartStop(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue