package main import ( "bytes" "encoding/json" "fmt" "io" "log/slog" "net/http" "net/http/httptest" "strings" "testing" "git.runcible.io/learning/pulley/internal/assert" ) func newTestApplication() application { cfg := config{env: "test"} return application{config: cfg, logger: slog.New(slog.NewTextHandler(io.Discard, nil))} } func TestHealthRoute(t *testing.T) { respRec := httptest.NewRecorder() r, err := http.NewRequest(http.MethodGet, "/v1/healthcheck", nil) if err != nil { t.Fatal(err) } app := newTestApplication() app.routes().ServeHTTP(respRec, r) resp := respRec.Result() if resp.StatusCode != http.StatusOK { t.Fatalf("Got status code %q, wanted status code %q", resp.StatusCode, http.StatusOK) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) assert.NilError(t, err) jsonContent := make(map[string]any) json.Unmarshal(body, &jsonContent) assert.Equal(t, jsonContent["status"], "available") } func TestCreateMovieHandler(t *testing.T) { respRec := httptest.NewRecorder() requestBody := `{"title": "Moana", "year": 2019, "runtime": "120 mins", "genres": ["family", "Samoan"]}` r, err := http.NewRequest(http.MethodPost, "/v1/movies", strings.NewReader(requestBody)) if err != nil { t.Fatal(err) } app := newTestApplication() app.routes().ServeHTTP(respRec, r) resp := respRec.Result() assert.Equal(t, resp.StatusCode, http.StatusCreated) defer resp.Body.Close() body, err := io.ReadAll(resp.Body) assert.NilError(t, err) body = bytes.TrimSpace(body) assert.StringContains(t, string(body), "Moana") } // Consider simply testing app.jsonReader func TestCreateMovieError(t *testing.T) { tests := []struct { name string input *strings.Reader wantBody string wantCode int }{ { name: "Test XML", input: strings.NewReader(`Alex`), wantBody: "body contains badly-formed JSON", wantCode: http.StatusBadRequest, }, { name: "Test Bad JSON", input: strings.NewReader(`{"title": "Moana", }`), wantBody: "body contains badly-formed JSON", wantCode: http.StatusBadRequest, }, { name: "Send a JSON array instead of an object", input: strings.NewReader(`["not", "good"]`), wantBody: "body contains incorrect JSON type", wantCode: http.StatusBadRequest, }, { name: "Send a numeric 'title' value", input: strings.NewReader(`{"title": 123}`), wantBody: "body contains incorrect JSON type", wantCode: http.StatusBadRequest, }, { name: "Send an empty request body", input: strings.NewReader(""), wantBody: "body must not be empty", wantCode: http.StatusBadRequest, }, { name: "Send unknown field", input: strings.NewReader(`{"title": "Moana", "year": 2019, "runtime": "120 mins", "genres": ["family", "Samoan"], "rating": "PG"}`), wantBody: "body contains unknown key", wantCode: http.StatusBadRequest, }, { name: "Send garbage after JSON", input: strings.NewReader(`{"title": "Moana"} :~()`), wantBody: "body must only contain a single JSON value", wantCode: http.StatusBadRequest, }, { name: "Send too large a JSON payload", // 1.5 MB title input: strings.NewReader(fmt.Sprintf("{\"title\": \"%s\"}", strings.Repeat("a", int(1.5*1024*1024)))), wantBody: "body must not be larger than 1048576 bytes", wantCode: http.StatusBadRequest, }, { name: "Send invalid runtime", input: strings.NewReader(`{"title": "Moana", "runtime": 120}`), wantBody: "invalid runtime format", wantCode: http.StatusBadRequest, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { respRec := httptest.NewRecorder() r, err := http.NewRequest(http.MethodPost, "/v1/movies", test.input) if err != nil { t.Fatal(err) } app := newTestApplication() app.routes().ServeHTTP(respRec, r) resp := respRec.Result() assert.Equal(t, resp.StatusCode, test.wantCode) var jsonResp map[string]string json.NewDecoder(resp.Body).Decode(&jsonResp) assert.StringContains(t, jsonResp["error"], test.wantBody) }) } } func TestGetAllMoviesHandler(t *testing.T) { testTable := []struct { name string id string wantCode int useID bool }{ { name: "Get Movie By ID", id: "1337", wantCode: 200, }, { name: "No ID provided", id: "", wantCode: 404, }, { name: "Negative ID", id: "-1", wantCode: 404, }, } for _, test := range testTable { t.Run(test.name, func(t *testing.T) { respRec := httptest.NewRecorder() r, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/v1/movies/%s", test.id), nil) t.Logf("Path: %s, Method: %s", r.URL.Path, r.Method) assert.NilError(t, err) app := newTestApplication() // want to test with httprouter since we use it to parse context app.routes().ServeHTTP(respRec, r) resp := respRec.Result() t.Logf("Code: %d", resp.StatusCode) assert.Equal(t, resp.StatusCode, test.wantCode) }) } }