github.com/isavita/tictactoe_api@v0.0.0-20230610152111-9a1d2166c39d/cmd/server/main_test.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "io" 6 "net/http" 7 "net/http/httptest" 8 "os" 9 "reflect" 10 "strings" 11 "sync" 12 "testing" 13 14 "github.com/isavita/tictactoe_api/internal/api" 15 "github.com/isavita/tictactoe_api/internal/game" 16 "github.com/isavita/tictactoe_api/internal/model" 17 ) 18 19 func TestMain(m *testing.M) { 20 // path setup to the root directory 21 // we need this because the main file is in `/cmd/server` 22 prevPath, _ := os.Getwd() 23 os.Chdir("../..") 24 defer os.Chdir(prevPath) 25 26 m.Run() 27 } 28 29 func TestTicTacToeHandler(t *testing.T) { 30 t.Run("first move with empty payload", func(t *testing.T) { 31 // setup the tic-tac-toe api 32 ticTacToeGame := game.NewTicTacToeGame() 33 ticTacToeAPI := api.NewTicTacToeAPI(ticTacToeGame) 34 35 s := httptest.NewServer(http.HandlerFunc(ticTacToeAPI.TicTacToeHandler)) 36 url := s.URL + "/v1/tictactoe" 37 payload := strings.NewReader(`{}`) 38 req, err := http.NewRequest(http.MethodPost, url, payload) 39 assertNoError(t, err) 40 41 resp, err := http.DefaultClient.Do(req) 42 assertNoError(t, err) 43 44 defer resp.Body.Close() 45 body, err := io.ReadAll(resp.Body) 46 assertNoError(t, err) 47 assertStatusCode(t, resp, http.StatusOK) 48 49 got := model.MoveResponse{} 50 err = json.Unmarshal(body, &got) 51 assertNoError(t, err) 52 53 want := model.MoveResponse{ 54 Success: true, 55 Message: "Player 1 has placed 'X' in position 1. Please note: If the user is playing with 'X', disregard this move. Instead, ask the user where they would like to place their first move, then present the game board reflecting their choice.", 56 Board: []int{1, 0, 0, 0, 0, 0, 0, 0, 0}, 57 BoardSize: 3, 58 BoardDisplay: " X | 2 | 3 \n --------- \n 4 | 5 | 6 \n --------- \n 7 | 8 | 9 ", 59 GameStatus: "ongoing", 60 NextPlayer: game.OPlayer, 61 } 62 63 if !reflect.DeepEqual(got, want) { 64 t.Errorf("got %v want %v", got, want) 65 } 66 }) 67 68 t.Run("handles concurrent requests", func(t *testing.T) { 69 s := newTestServer() 70 url := s.URL + "/v1/tictactoe" 71 wg := &sync.WaitGroup{} 72 73 for i := 0; i < 10; i++ { 74 wg.Add(1) 75 go func(w *sync.WaitGroup) { 76 defer w.Done() 77 payload := strings.NewReader(`{}`) 78 req, _ := http.NewRequest(http.MethodPost, url, payload) 79 _, err := http.DefaultClient.Do(req) 80 assertNoError(t, err) 81 }(wg) 82 } 83 wg.Wait() 84 }) 85 86 } 87 88 func TestOpenAIPluginHandler(t *testing.T) { 89 t.Parallel() 90 91 req := httptest.NewRequest(http.MethodGet, "/.well-known/ai-plugin.json", nil) 92 recorder := httptest.NewRecorder() 93 openAIPluginHandler(recorder, req) 94 95 res := recorder.Result() 96 defer res.Body.Close() 97 98 assertStatusCode(t, res, http.StatusOK) 99 } 100 101 func TestOpenapiHandler(t *testing.T) { 102 t.Parallel() 103 104 req := httptest.NewRequest(http.MethodGet, "/openapi.yaml", nil) 105 recorder := httptest.NewRecorder() 106 openapiHandler(recorder, req) 107 108 res := recorder.Result() 109 defer res.Body.Close() 110 111 assertStatusCode(t, res, http.StatusOK) 112 } 113 114 func TestLogoHandler(t *testing.T) { 115 t.Parallel() 116 117 req := httptest.NewRequest(http.MethodGet, "/logo.png", nil) 118 recorder := httptest.NewRecorder() 119 openapiHandler(recorder, req) 120 res := recorder.Result() 121 defer res.Body.Close() 122 123 assertStatusCode(t, res, http.StatusOK) 124 } 125 126 func BenchmarkTicTacToeHandler(b *testing.B) { 127 s := newTestServer() 128 defer s.Close() 129 130 url := s.URL + "/v1/tictactoe" 131 for i := 0; i < b.N; i++ { 132 payload := strings.NewReader(`{}`) 133 req, _ := http.NewRequest(http.MethodPost, url, payload) 134 _, err := http.DefaultClient.Do(req) 135 assertNoError(b, err) 136 } 137 } 138 139 func BenchmarkOpenAIPluginHandler(b *testing.B) { 140 s := newTestServer() 141 defer s.Close() 142 143 url := s.URL + "/.well-known/ai-plugin.json" 144 for i := 0; i < b.N; i++ { 145 req, _ := http.NewRequest(http.MethodGet, url, nil) 146 _, err := http.DefaultClient.Do(req) 147 assertNoError(b, err) 148 } 149 } 150 151 func BenchmarkOpenapiHandler(b *testing.B) { 152 s := newTestServer() 153 defer s.Close() 154 155 url := s.URL + "/openapi.yaml" 156 for i := 0; i < b.N; i++ { 157 req, _ := http.NewRequest(http.MethodGet, url, nil) 158 _, err := http.DefaultClient.Do(req) 159 assertNoError(b, err) 160 } 161 } 162 163 func BenchmarkLogoHandler(b *testing.B) { 164 s := newTestServer() 165 defer s.Close() 166 167 url := s.URL + "/logo.png" 168 for i := 0; i < b.N; i++ { 169 req, _ := http.NewRequest(http.MethodGet, url, nil) 170 _, err := http.DefaultClient.Do(req) 171 assertNoError(b, err) 172 } 173 } 174 175 func assertStatusCode(t testing.TB, res *http.Response, want int) { 176 if res.StatusCode != want { 177 t.Errorf("got %v want %v", res.StatusCode, want) 178 } 179 } 180 181 func assertNoError(t testing.TB, err error) { 182 t.Helper() 183 if err != nil { 184 t.Errorf("got error %v when no error was expected", err) 185 } 186 } 187 188 func newTestServer() *httptest.Server { 189 ticTacToeGame := game.NewTicTacToeGame() 190 ticTacToeAPI := api.NewTicTacToeAPI(ticTacToeGame) 191 return httptest.NewServer(http.HandlerFunc(ticTacToeAPI.TicTacToeHandler)) 192 }