github.com/speedoops/go-gqlrest-demo@v0.0.0-20220710122636-5c6460aeab4f/graph/todo.resolvers_test.go (about)

     1  package graph
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"encoding/json"
     9  
    10  	gqlcli "github.com/99designs/gqlgen/client"
    11  	"github.com/speedoops/go-gqlrest-demo/graph/engine"
    12  	"github.com/speedoops/go-gqlrest-demo/graph/model"
    13  	restcli "github.com/speedoops/go-gqlrest/client"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  const T9527 = "T9527" // 这是一个特定的值,第一个创建的 Todo 的 ID
    18  
    19  type Todo struct {
    20  	ID         string           `json:"id"`
    21  	Text       string           `json:"text"`
    22  	Done       bool             `json:"done"`
    23  	User       model.User       `json:"user"`
    24  	Type       *model.TodoType  `json:"type"`
    25  	Categories []model.Category `json:"categories"`
    26  }
    27  
    28  func TestTodo(t *testing.T) {
    29  	srv := engine.NewMockServer(&Resolver{})
    30  	c := gqlcli.New(srv, gqlcli.Path("/query"))
    31  
    32  	t.Run("mutation.createTodo", func(t *testing.T) {
    33  		var resp struct {
    34  			Todo Todo `json:"createTodo"`
    35  		}
    36  
    37  		mutation := `
    38  		mutation createTodo($text:String!) {
    39  			createTodo(input: {userID:"uid", text:$text}){
    40  			  id,text,done
    41  			}
    42  		  }
    43  		`
    44  
    45  		genRandomText := func() string {
    46  			return fmt.Sprintf("text_%s", time.Now().Format("2006-01-02 15:04:05"))
    47  		}
    48  
    49  		c.MustPost(mutation, &resp, gqlcli.Var("text", genRandomText()))
    50  		c.MustPost(mutation, &resp, gqlcli.Var("text", genRandomText()))
    51  		c.MustPost(mutation, &resp, gqlcli.Var("text", genRandomText()))
    52  
    53  		require.NotEmpty(t, resp.Todo)
    54  		t.Logf("%+v", resp.Todo)
    55  	})
    56  
    57  	t.Run("mutation.completeTodo", func(t *testing.T) {
    58  		var resp struct {
    59  			Todo Todo `json:"completeTodo"`
    60  		}
    61  
    62  		mutation := `
    63  		mutation completeTodo($id: ID!) {
    64  			completeTodo(id: $id){
    65  			  id,text,done
    66  			}
    67  		  }
    68  		`
    69  		c.MustPost(mutation, &resp, gqlcli.Var("id", T9527))
    70  
    71  		require.NotEmpty(t, resp.Todo)
    72  		t.Logf("%+v", resp.Todo)
    73  	})
    74  
    75  	t.Run("mutation.updateTodo", func(t *testing.T) {
    76  		var resp struct {
    77  			Todo Todo `json:"updateTodo"`
    78  		}
    79  
    80  		mutation := `
    81  		mutation updateTodo($id: ID!) {
    82  			updateTodo(input: {id: $id, userID:"uid", text:"9527.Updated"}){
    83  			  id,text,done
    84  			}
    85  		  }
    86  		`
    87  		c.MustPost(mutation, &resp, gqlcli.Var("id", T9527))
    88  
    89  		require.NotEmpty(t, resp.Todo)
    90  		t.Logf("%+v", resp.Todo)
    91  	})
    92  
    93  	t.Run("query.todos should contain T9527", func(t *testing.T) {
    94  		var resp struct {
    95  			Todos []Todo `json:"todos"`
    96  		}
    97  		query := `
    98  		query todos {
    99  			todos(ids:["T9527"],types:[TypeA,TypeB],text2:["text1","text2"],done2:[true,false]) {
   100  				id,text,done
   101  			}
   102  		}
   103  		`
   104  		c.MustPost(query, &resp)
   105  
   106  		require.NotEmpty(t, resp.Todos)
   107  		for _, v := range resp.Todos {
   108  			if v.ID == T9527 {
   109  				return
   110  			}
   111  		}
   112  		require.Fail(t, "T9527 not found")
   113  		t.Logf("%+v", resp.Todos)
   114  	})
   115  
   116  	t.Run("mutation.deleteTodo", func(t *testing.T) {
   117  		var resp struct {
   118  			Result bool `json:"deleteTodo"`
   119  		}
   120  
   121  		mutation := `
   122  		mutation deleteTodo($id: ID!) {
   123  			deleteTodo(id: $id)
   124  		  }
   125  		`
   126  		c.MustPost(mutation, &resp, gqlcli.Var("id", T9527))
   127  
   128  		require.NotEmpty(t, resp.Result)
   129  		t.Logf("%+v", resp.Result)
   130  	})
   131  
   132  	t.Run("query.todos should not contain T9527", func(t *testing.T) {
   133  		var resp struct {
   134  			Todos []Todo `json:"todos"`
   135  		}
   136  		query := `
   137  		query todos {
   138  			todos(types:[TypeA,TypeB],text2:["text1","text2"],done2:[true,false]) {
   139  				id,text,done
   140  			}
   141  		}
   142  		`
   143  		c.MustPost(query, &resp)
   144  
   145  		require.NotEmpty(t, resp.Todos)
   146  		for _, v := range resp.Todos {
   147  			require.NotEqual(t, v.ID, T9527)
   148  		}
   149  		t.Logf("%+v", resp.Todos)
   150  	})
   151  }
   152  
   153  func FoundT9527(data []Todo) bool {
   154  	for _, v := range data {
   155  		if v.ID == T9527 {
   156  			return true
   157  		}
   158  	}
   159  	return false
   160  }
   161  
   162  func TestTodos_REST(t *testing.T) {
   163  	s := engine.NewMockServer(&Resolver{})
   164  	c := restcli.New(s, restcli.Prefix(""))
   165  
   166  	t.Run("rest.createTodo", func(t *testing.T) {
   167  		var resp struct {
   168  			Code    int
   169  			Message string
   170  			Data    Todo
   171  		}
   172  
   173  		payload := `{"input": {"userID":"uid", "text":"$text"}}`
   174  		err := c.Post("/api/v1/todos", &resp, restcli.Body(payload))
   175  		_ = c.Post("/api/v1/todos", &resp, restcli.Body(payload))
   176  		_ = c.Post("/api/v1/todos", &resp, restcli.Body(payload))
   177  		require.Nil(t, err)
   178  		require.NotEmpty(t, resp.Data)
   179  
   180  		t.Logf("%+v", resp.Data)
   181  	})
   182  
   183  	t.Run("rest.completeTodo", func(t *testing.T) {
   184  		var resp struct {
   185  			Code    int
   186  			Message string
   187  			Data    Todo
   188  		}
   189  
   190  		err := c.Post("/api/v1/todo/T9527/complete", &resp)
   191  		require.Nil(t, err)
   192  		require.NotEmpty(t, resp.Data)
   193  
   194  		t.Logf("%+v", resp.Data)
   195  	})
   196  
   197  	t.Run("rest.completeTodos", func(t *testing.T) {
   198  		var resp struct {
   199  			Code    int
   200  			Message string
   201  			Data    Todo
   202  		}
   203  
   204  		payload := `{"ids": ["uid", "text"]}`
   205  		err := c.Post("/api/v1/todos/bulk-complete", &resp, restcli.Body(payload))
   206  		require.Nil(t, err)
   207  
   208  		t.Logf("%+v", resp.Data)
   209  	})
   210  
   211  	t.Run("rest.updateTodo", func(t *testing.T) {
   212  		var resp struct {
   213  			Code    int
   214  			Message string
   215  			Data    Todo
   216  		}
   217  
   218  		payload := `{"input": {"userID":"uid", "text":"$text.Updated"}}`
   219  		err := c.Put("/api/v1/todo/T9527", &resp, restcli.Body(payload))
   220  		require.Nil(t, err)
   221  		require.NotEmpty(t, resp.Data)
   222  
   223  		t.Logf("%+v", resp.Data)
   224  	})
   225  
   226  	t.Run("rest.todos: filter by ids", func(t *testing.T) {
   227  		var resp struct {
   228  			Code    int
   229  			Message string
   230  			Data    []Todo
   231  		}
   232  
   233  		err := c.Get("/api/v1/todos?ids=T9527&types=TypeA,TypeB&text2=text1,text2&done2=true,false", &resp)
   234  		require.Nil(t, err)
   235  		if !FoundT9527(resp.Data) {
   236  			t.Fail()
   237  		}
   238  		t.Logf("%+v", resp.Data)
   239  
   240  		err = c.Get("/api/v1/todos?ids=FakeID111,FakeID222&types=TypeA,TypeB&text2=text1,text2&done2=true,false", &resp)
   241  		require.Nil(t, err)
   242  		if FoundT9527(resp.Data) {
   243  			t.Fail()
   244  		}
   245  		t.Logf("%+v", resp.Data)
   246  	})
   247  
   248  	t.Run("rest.deleteTodo", func(t *testing.T) {
   249  		err := c.Delete("/api/v1/todo/T9527", nil)
   250  		require.Nil(t, err)
   251  	})
   252  
   253  	t.Run("rest.todos: check after deletion", func(t *testing.T) {
   254  		var resp struct {
   255  			Code    int
   256  			Message string
   257  			Data    []Todo
   258  		}
   259  
   260  		err := c.Get("/api/v1/todos?ids=T9527,T666&types=TypeA,TypeB&text2=text1,text2&done2=true,false", &resp)
   261  		require.Nil(t, err)
   262  
   263  		for _, v := range resp.Data {
   264  			if v.ID == T9527 {
   265  				t.Fail()
   266  			}
   267  		}
   268  		t.Logf("%+v", resp.Data)
   269  	})
   270  }
   271  
   272  func TestTodos_POST(t *testing.T) {
   273  	s := engine.NewMockServer(&Resolver{})
   274  	c := restcli.New(s, restcli.Prefix(""))
   275  
   276  	t.Run("rest.createTodo", func(t *testing.T) {
   277  		var resp struct {
   278  			Code    int
   279  			Message string
   280  			Data    Todo
   281  		}
   282  
   283  		payload := `{"userID":"uid", "text":"$text"}` // 方式1
   284  		//payload = fmt.Sprintf(`{"input": %s}`, payload) // 方式2
   285  
   286  		err := c.Post("/api/v1/todos", &resp, restcli.Body(payload))
   287  		require.Nil(t, err)
   288  		require.NotEmpty(t, resp.Data)
   289  
   290  		t.Logf("%+v", resp)
   291  	})
   292  }
   293  
   294  func TestTodos_GET(t *testing.T) {
   295  	s := engine.NewMockServer(&Resolver{})
   296  	c := restcli.New(s, restcli.Prefix(""))
   297  
   298  	t.Run("rest.todos", func(t *testing.T) {
   299  		var resp struct {
   300  			Code    int
   301  			Message string
   302  			Data    []Todo
   303  		}
   304  
   305  		err := c.Get("/api/v1/todos?ids=T9527&types=TypeA,TypeB&text2=text1,text2&done2=true,false", &resp)
   306  		require.Nil(t, err)
   307  
   308  		for _, v := range resp.Data {
   309  			if v.ID == T9527 {
   310  				t.Fail()
   311  			}
   312  		}
   313  		t.Logf("%+v", resp.Data)
   314  	})
   315  }
   316  
   317  func TestTodos_PUT(t *testing.T) {
   318  	s := engine.NewMockServer(&Resolver{})
   319  	c := restcli.New(s, restcli.Prefix(""))
   320  
   321  	t.Run("rest.updateTodo", func(t *testing.T) {
   322  		var resp struct {
   323  			//nolint:staticcheck,revive // ignore SA5008: unknown JSON option "squash"
   324  			Todo `json:",squash"`
   325  		}
   326  
   327  		payload := `{"text":"$text.Updated"}`
   328  
   329  		err := c.Put("/api/v1/todo/T9527?userID=uid", &resp, restcli.Body(payload))
   330  		require.NotNil(t, err)
   331  		t.Logf("%+v", err)
   332  		require.Contains(t, err.Error(), "http 404")
   333  	})
   334  }
   335  
   336  func TestTodos_DELETE(t *testing.T) {
   337  	s := engine.NewMockServer(&Resolver{})
   338  	c := restcli.New(s, restcli.Prefix(""))
   339  
   340  	t.Run("rest.deleteTodo", func(t *testing.T) {
   341  		err := c.Delete("/api/v1/todo/T9527", nil)
   342  		require.NotNil(t, err)
   343  		t.Logf("%+v", err)
   344  		require.Contains(t, err.Error(), "http 404")
   345  	})
   346  
   347  	t.Run("rest.deleteTodoByUser", func(t *testing.T) {
   348  		err := c.Delete("/api/v1/todos/?userID=T9527", nil)
   349  		require.NotNil(t, err)
   350  		t.Logf("%+v", err)
   351  		require.Contains(t, err.Error(), "http 404")
   352  	})
   353  }
   354  
   355  func TestCreateTodo_REST(t *testing.T) {
   356  	s := engine.NewMockServer(&Resolver{})
   357  	c := restcli.New(s, restcli.Prefix(""))
   358  
   359  	t.Run("测试普通字符串", func(t *testing.T) {
   360  		var resp struct {
   361  			Code    int    `json:"code"`
   362  			Message string `json:"message"`
   363  			Data    Todo   `json:"data"`
   364  		}
   365  
   366  		payload := `{"input": {"userID":"uid", "text":"$text"}}`
   367  		err := c.Post("/api/v1/todos", &resp, restcli.Body(payload))
   368  		require.Nil(t, err)
   369  		require.NotEmpty(t, resp.Data)
   370  
   371  		t.Logf("%+v", resp.Data)
   372  	})
   373  
   374  	t.Run("测试字符串,包含一个ASCII字符\u0001", func(t *testing.T) {
   375  		var resp struct {
   376  			Code    int
   377  			Message string
   378  			Data    Todo
   379  		}
   380  
   381  		var input struct {
   382  			Input model.NewTodoInput `json:"input"`
   383  		}
   384  		done := true
   385  		input.Input.UserID = "uid"
   386  		input.Input.Done = &done
   387  		input.Input.Text = "测试字符串,包含一个ASCII字符\u0001"
   388  		payload, err := json.Marshal(input)
   389  		require.Nil(t, err)
   390  
   391  		err = c.Post("/api/v1/todos", &resp, restcli.Body(string(payload)))
   392  		require.Nil(t, err)
   393  		require.NotEmpty(t, resp.Data)
   394  
   395  		t.Logf("%+v", resp.Data)
   396  	})
   397  
   398  	t.Run("测试字符串,包含多个ASCII字符\u0001", func(t *testing.T) {
   399  		var resp struct {
   400  			Code    int
   401  			Message string
   402  			Data    Todo
   403  		}
   404  
   405  		var input struct {
   406  			Input model.NewTodoInput `json:"input"`
   407  		}
   408  
   409  		done := true
   410  		input.Input.Done = &done
   411  		input.Input.UserID = "uid"
   412  		input.Input.Text = "\u0001测试字符串,包含多个\u0001包含ASCII字符\u0001"
   413  		payload, err := json.Marshal(input)
   414  		require.Nil(t, err)
   415  
   416  		err = c.Post("/api/v1/todos", &resp, restcli.Body(string(payload)))
   417  		require.Nil(t, err)
   418  		require.NotEmpty(t, resp.Data)
   419  
   420  		t.Logf("%+v", resp.Data)
   421  	})
   422  
   423  	t.Run("测试字符串存储json对象,对象包含ASCII字符\u0001", func(t *testing.T) {
   424  		var resp struct {
   425  			Code    int
   426  			Message string
   427  			Data    Todo
   428  		}
   429  
   430  		var obj struct {
   431  			Seq  int    `json:"seq"`
   432  			Data string `json:"data"`
   433  		}
   434  
   435  		obj.Seq = 100
   436  		obj.Data = "\u0001json对象包含多个\u0001ASCII字符\u0001"
   437  		byteData, err := json.Marshal(obj)
   438  
   439  		var input struct {
   440  			Input model.NewTodoInput `json:"input"`
   441  		}
   442  
   443  		done := true
   444  		input.Input.Done = &done
   445  		input.Input.UserID = "uid"
   446  		input.Input.Text = "字符串存储json对象\u0001," + string(byteData)
   447  
   448  		payload, err := json.Marshal(input)
   449  		require.Nil(t, err)
   450  
   451  		err = c.Post("/api/v1/todos", &resp, restcli.Body(string(payload)))
   452  		require.Nil(t, err)
   453  		require.NotEmpty(t, resp.Data)
   454  
   455  		t.Logf("%+v", resp.Data)
   456  	})
   457  }