package ron import ( "fmt" "log/slog" "net/http" "net/http/httptest" "os" "ron/testhelpers" "testing" ) func TestMain(m *testing.M) { os.Mkdir("templates", os.ModePerm) f, _ := os.Create("templates/page.index.gohtml") f.WriteString("
layout.base.gohtml
{{ .Data.foo }}
{{ block \"base/content\" . }}{{ end }}{{ end }}")) f.Close() f, _ = os.Create("templates/layout.another.gohtml") f.Write([]byte("{{ define \"layout/another\" }}layout.another.gohtml
{{ .Data.bar }}
{{ block \"base/content\" . }}{{ end }}{{ end }}")) f.Close() f, _ = os.Create("templates/fragment.button.gohtml") f.Close() f, _ = os.Create("templates/component.list.gohtml") f.Close() f, _ = os.Create("templates/page.tindex.gohtml") f.Write([]byte("{{ template \"layout/base\" .}}{{ define \"base/content\" }}page.tindex.gohtml
{{ .Data.bar }}
{{ end }}")) f.Close() f, _ = os.Create("templates/page.another.gohtml") f.Write([]byte("{{ template \"layout/another\" .}}{{ define \"base/content\" }}page.another.gohtml
{{ .Data.foo }}
{{ end }}")) f.Close() os.Mkdir("assets", os.ModePerm) f, _ = os.Create("assets/style.css") f.WriteString("body { background-color: red; }") f.Close() f, _ = os.Create("assets/script.js") f.WriteString("console.log('Hello, World!');") f.Close() code := m.Run() os.RemoveAll("templates") os.RemoveAll("assets") os.Exit(code) } func Test_defaultEngine(t *testing.T) { e := defaultEngine() if e == nil { t.Error("Expected Engine, Actual: nil") } } func Test_New(t *testing.T) { e := New() if e == nil { t.Error("Expected Engine, Actual: nil") } if e.Render != nil { t.Error("No expected Renderer, Actual: Renderer") } } func Test_applyEngineConfig(t *testing.T) { e := New(func(e *Engine) { e.Render = NewHTMLRender() e.Config.LogLevel = slog.LevelInfo }) if e.Render == nil { t.Error("Expected Renderer, Actual: nil") } if e.Config.LogLevel != slog.LevelInfo { t.Errorf("Expected LogLevel: 1, Actual: %d", e.Config.LogLevel) } } func Test_ServeHTTP(t *testing.T) { e := New() api := e.GROUP("/api") api.GET("/index", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("GET API")) }) rr := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/index", nil) e.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) } } func Test_RUN(t *testing.T) { e := New() go func() { e.Run(":8080") }() } func Test_createStack(t *testing.T) { m1 := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Middleware 1")) next.ServeHTTP(w, r) }) } m2 := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Middleware 2")) next.ServeHTTP(w, r) }) } stack := createStack(m1, m2) rr := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/", nil) stack(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Handler")) })).ServeHTTP(rr, req) if rr.Body.String() != "Middleware 1Middleware 2Handler" { t.Errorf("Expected: Middleware 1Middleware 2Handler, Actual: %s", rr.Body.String()) } } func Test_USE(t *testing.T) { e := New() m1 := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Middleware 1")) next.ServeHTTP(w, r) }) } m2 := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Middleware 2")) next.ServeHTTP(w, r) }) } e.USE(m1) e.USE(m2) rr := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/", nil) e.ServeHTTP(rr, req) if rr.Body.String() != "Middleware 1Middleware 2404 page not found\n" { t.Errorf("Expected: Middleware 1Middleware 2Handler, Actual: %s", rr.Body.String()) } } func Test_GET(t *testing.T) { e := New() tests := []struct { name string method string path string expectedStatus int expectedBody string }{ {"root endpoint", "GET", "/", http.StatusOK, "GET Root"}, {"api endpoint", "GET", "/api", http.StatusOK, "GET API"}, {"api endpoint with version", "GET", "/api/v1", http.StatusOK, "GET API v1"}, {"resource with param", "GET", "/api/v1/resource/1", http.StatusOK, "GET Resource"}, } e.GET("/", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("GET Root")) }) e.GET("/api", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("GET API")) }) e.GET("/api/v1", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("GET API v1")) }) e.GET("/api/v1/resource/{id}", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("GET Resource")) }) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rr := httptest.NewRecorder() req, _ := http.NewRequest(tt.method, tt.path, nil) e.ServeHTTP(rr, req) if rr.Code != tt.expectedStatus { t.Errorf("Expected status code: %d, Actual: %d", tt.expectedStatus, rr.Code) } if rr.Body.String() != tt.expectedBody { t.Errorf("Expected body: %q, Actual: %q", tt.expectedBody, rr.Body.String()) } }) } } func Test_POST(t *testing.T) { e := New() e.POST("/", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("POST")) }) rr := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/", nil) e.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) } if rr.Body.String() != "POST" { t.Errorf("Expected: POST, Actual: %s", rr.Body.String()) } } func Test_PUT(t *testing.T) { e := New() e.PUT("/", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("PUT")) }) rr := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/", nil) e.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) } if rr.Body.String() != "PUT" { t.Errorf("Expected: PUT, Actual: %s", rr.Body.String()) } } func Test_DELETE(t *testing.T) { e := New() e.DELETE("/", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("DELETE")) }) rr := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/", nil) e.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) } if rr.Body.String() != "DELETE" { t.Errorf("Expected: DELETE, Actual: %s", rr.Body.String()) } } func Test_GROUP(t *testing.T) { e := New() api := e.GROUP("/api") api.GET("/index", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("GET API")) }) rr := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/index", nil) e.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) } if rr.Body.String() != "GET API" { t.Errorf("Expected: GET API, Actual: %s", rr.Body.String()) } } func Test_GROUPWithMiddleware(t *testing.T) { e := New() e.GET("/index", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("GET Root")) }) e.USE(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Middleware 1")) next.ServeHTTP(w, r) }) }) api := e.GROUP("/api") api.USE(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Middleware 2")) next.ServeHTTP(w, r) }) }) api.GET("/index", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("GET API")) }) rr := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/index", nil) e.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) } if rr.Body.String() != "Middleware 1Middleware 2GET API" { t.Errorf("Expected: Middleware 1Middleware 2GET API, Actual: %s", rr.Body.String()) } } func Test_GROUPPOST(t *testing.T) { e := New() api := e.GROUP("/api") api.POST("/index", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("POST API")) }) rr := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/index", nil) e.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) } if rr.Body.String() != "POST API" { t.Errorf("Expected: POST API, Actual: %s", rr.Body.String()) } } func Test_GROUPPUT(t *testing.T) { e := New() api := e.GROUP("/api") api.PUT("/index", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("PUT API")) }) rr := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/index", nil) e.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) } if rr.Body.String() != "PUT API" { t.Errorf("Expected: PUT API, Actual: %s", rr.Body.String()) } } func Test_GROUPDELETE(t *testing.T) { e := New() api := e.GROUP("/api") api.DELETE("/index", func(c *CTX) { c.W.WriteHeader(http.StatusOK) c.W.Write([]byte("DELETE API")) }) rr := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/index", nil) e.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Expected status code: %d, Actual: %d", http.StatusOK, status) } if rr.Body.String() != "DELETE API" { t.Errorf("Expected: DELETE API, Actual: %s", rr.Body.String()) } } func Test_Static(t *testing.T) { tests := map[string]struct { givenPath string givenFile string givenDirectory string expectedResponse testhelpers.ExpectedResponse }{ "valid css path": { givenPath: "assets", givenFile: "style.css", givenDirectory: "assets", expectedResponse: testhelpers.ExpectedResponse{ Code: http.StatusOK, Header: HeaderCSS_UTF8, Body: "body { background-color: red; }", }, }, "valid js path": { givenPath: "assets", givenFile: "script.js", givenDirectory: "assets", expectedResponse: testhelpers.ExpectedResponse{ Code: http.StatusOK, Header: HeaderAppJS_UTF8, Body: "console.log('Hello, World!');", }, }, "invalid path": { givenPath: "assets", givenFile: "nonexistent.css", givenDirectory: "assets", expectedResponse: testhelpers.ExpectedResponse{ Code: http.StatusNotFound, Header: HeaderPlain_UTF8, Body: "404 page not found\n", }, }, "invalid directory": { givenPath: "assets", givenFile: "style.css", givenDirectory: "nonexistent", expectedResponse: testhelpers.ExpectedResponse{ Code: http.StatusNotFound, Header: HeaderPlain_UTF8, Body: "404 page not found\n", }, }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { t.Parallel() rr := httptest.NewRecorder() req, _ := http.NewRequest("GET", fmt.Sprintf("/%s/%s", tt.givenPath, tt.givenFile), nil) e := New() e.Static(tt.givenPath, tt.givenDirectory) e.ServeHTTP(rr, req) testhelpers.VerifyResponse(t, rr, tt.expectedResponse) }) } } type Foo struct { Bar string `json:"bar"` Taz int `json:"something"` Car *string `json:"car"` } func Test_JSON(t *testing.T) { tests := map[string]struct { givenCode int givenData any expectedResponse testhelpers.ExpectedResponse }{ "valid JSON": { givenCode: http.StatusOK, givenData: Foo{Bar: "bar", Taz: 30, Car: nil}, expectedResponse: testhelpers.ExpectedResponse{ Code: http.StatusOK, Header: HeaderJSON, Body: `{"bar":"bar","something":30,"car":null}` + "\n", }, }, "invalid JSON": { givenCode: http.StatusOK, givenData: make(chan int), expectedResponse: testhelpers.ExpectedResponse{ Code: http.StatusInternalServerError, Header: HeaderPlain_UTF8, Body: "json: unsupported type: chan int\n", }, }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { t.Parallel() rr := httptest.NewRecorder() c := &CTX{ W: &responseWriterWrapper{ResponseWriter: rr}, } c.JSON(tt.givenCode, tt.givenData) testhelpers.VerifyResponse(t, rr, tt.expectedResponse) }) } } func Test_HTML(t *testing.T) { tests := map[string]struct { givenCode int givenTemplate string givenData *TemplateData expectedResponse testhelpers.ExpectedResponse }{ "valid HTML": { givenCode: http.StatusOK, givenTemplate: "page.index.gohtml", givenData: &TemplateData{Data: Data{"heading1": "foo", "heading2": "bar"}}, expectedResponse: testhelpers.ExpectedResponse{ Code: http.StatusOK, Header: HeaderHTML_UTF8, Body: "