diff --git a/testing/codegen/harness.go b/testing/codegen/harness.go index ae018dc27..018be4c58 100644 --- a/testing/codegen/harness.go +++ b/testing/codegen/harness.go @@ -206,7 +206,7 @@ func testingPath(_ string, svc *expr.ServiceExpr) string { // hasStreams checks if the service has streaming methods. func hasStreams(svc *expr.ServiceExpr) bool { for _, m := range svc.Methods { - if m.Stream != expr.NoStreamKind { + if m.IsStreaming() { return true } } diff --git a/testing/codegen/harness_test.go b/testing/codegen/harness_test.go new file mode 100644 index 000000000..e8921a252 --- /dev/null +++ b/testing/codegen/harness_test.go @@ -0,0 +1,57 @@ +package codegen + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "goa.design/goa/v3/codegen" + "goa.design/goa/v3/codegen/service" + httpcodegen "goa.design/goa/v3/http/codegen" + "goa.design/plugins/v3/testing/codegen/testdata" +) + +func TestGenerateHarness(t *testing.T) { + cases := map[string]struct { + DSL func() + Code map[string][]string + Path string + }{ + "with-stream": { + DSL: testdata.WithStreamDSL, + Code: map[string][]string{ + "http-harness": {testdata.WithStreamCode}, + }, + Path: "gen/with_stream_service/with_stream_servicetest/harness.go", + }, + "without-stream": { + DSL: testdata.WithoutStreamDSL, + Code: map[string][]string{ + "http-harness": {testdata.WithoutStreamCode}, + }, + Path: "gen/without_stream_service/without_stream_servicetest/harness.go", + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + root := httpcodegen.RunHTTPDSL(t, c.DSL) + services := service.NewServicesData(root) + svc := root.Services[0] + svcData := services.Get(svc.Name) + f := generateHarness("", svcData, root, svc) + assert.Equal(t, c.Path, f.Path) + for sec, secCode := range c.Code { + testCode(t, f, sec, secCode) + } + }) + } +} + +func testCode(t *testing.T, file *codegen.File, section string, expCode []string) { + sections := file.Section(section) + require.Len(t, sections, len(expCode)) + for i, c := range expCode { + code := codegen.SectionCode(t, sections[i]) + assert.Equal(t, c, code) + } +} diff --git a/testing/codegen/templates/http_harness.go.tpl b/testing/codegen/templates/http_harness.go.tpl index 1f5b62644..a13e8e172 100644 --- a/testing/codegen/templates/http_harness.go.tpl +++ b/testing/codegen/templates/http_harness.go.tpl @@ -5,11 +5,13 @@ func (h *Harness) setupHTTP() { // Create HTTP handler mux := goahttp.NewMuxer() + {{- if .HasStreams }} // Create WebSocket upgrader for streaming endpoints upgrader := &websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } - server := httpsvr.New(endpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil, upgrader, nil) + {{- end }} + server := httpsvr.New(endpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil{{ if .HasStreams }}, upgrader, nil{{ end }}) httpsvr.Mount(mux, server) // Create test server @@ -31,46 +33,50 @@ func (h *Harness) HTTPClient() *http.Client { {{ printf "getHTTPClientImpl returns the underlying HTTP client implementation." | comment }} func (h *Harness) getHTTPClientImpl() *httpcli.Client { - if h.httpSvr == nil || h.httpCli == nil { - h.t.Fatal("HTTP transport not configured") - } - u, err := url.Parse(h.httpSvr.URL) - if err != nil { - h.t.Fatalf("invalid test server URL: %v", err) - } - scheme := u.Scheme - host := u.Host - // Create WebSocket dialer for streaming endpoints - wsDialer := &websocket.Dialer{ - Proxy: http.ProxyFromEnvironment, - } - - return httpcli.NewClient( - scheme, - host, - h.httpCli, - goahttp.RequestEncoder, - goahttp.ResponseDecoder, - false, - wsDialer, - nil, - ) + if h.httpSvr == nil || h.httpCli == nil { + h.t.Fatal("HTTP transport not configured") + } + u, err := url.Parse(h.httpSvr.URL) + if err != nil { + h.t.Fatalf("invalid test server URL: %v", err) + } + scheme := u.Scheme + host := u.Host + {{- if .HasStreams }} + // Create WebSocket dialer for streaming endpoints + wsDialer := &websocket.Dialer{ + Proxy: http.ProxyFromEnvironment, + } + {{- end }} + + return httpcli.NewClient( + scheme, + host, + h.httpCli, + goahttp.RequestEncoder, + goahttp.ResponseDecoder, + false, + {{- if .HasStreams }} + wsDialer, + nil, + {{- end }} + ) } {{ printf "HTTPClientEndpoints creates HTTP client endpoints for the service." | comment }} func (h *Harness) HTTPClientEndpoints() *{{ .PkgName }}.Endpoints { - c := h.getHTTPClientImpl() - return &{{ .PkgName }}.Endpoints{ - {{- range .Methods }} - {{- $method := . }} - {{- range .Targets }} - {{- if or .IsHTTPPlain .IsHTTPServerSent .IsHTTPWebSocket }} - {{ $method.VarName }}: c.{{ $method.VarName }}(), - {{- break }} - {{- end }} - {{- end }} - {{- end }} - } + c := h.getHTTPClientImpl() + return &{{ .PkgName }}.Endpoints{ + {{- range .Methods }} + {{- $method := . }} + {{- range .Targets }} + {{- if or .IsHTTPPlain .IsHTTPServerSent .IsHTTPWebSocket }} + {{ $method.VarName }}: c.{{ $method.VarName }}(), + {{- break }} + {{- end }} + {{- end }} + {{- end }} + } } {{ printf "HTTPURL returns the base URL of the test HTTP server." | comment }} @@ -108,6 +114,7 @@ func (h *Harness) HTTPRequest(method, path string, body any) *http.Request { return req } +{{- if .HasStreams }} {{ printf "HTTPWSURL builds a websocket URL from the base HTTP URL and path." | comment }} func (h *Harness) HTTPWSURL(path string) string { base := h.HTTPURL() @@ -123,6 +130,7 @@ func (h *Harness) HTTPWSURL(path string) string { u.Path = path return u.String() } +{{- end }} {{ printf "HTTPDo performs an HTTP request and returns the response." | comment }} func (h *Harness) HTTPDo(req *http.Request) *http.Response { diff --git a/testing/codegen/testdata/code.go b/testing/codegen/testdata/code.go new file mode 100644 index 000000000..bc9707d1c --- /dev/null +++ b/testing/codegen/testdata/code.go @@ -0,0 +1,237 @@ +package testdata + +var WithStreamCode = `// setupHTTP initializes the HTTP test server and client. +func (h *Harness) setupHTTP() { + // Create endpoints + endpoints := withstreamservice.NewEndpoints(h.service) + + // Create HTTP handler + mux := goahttp.NewMuxer() + // Create WebSocket upgrader for streaming endpoints + upgrader := &websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { return true }, + } + server := httpsvr.New(endpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil, upgrader, nil) + httpsvr.Mount(mux, server) + + // Create test server + h.httpSvr = httptest.NewServer(mux) + + // Create HTTP client + h.httpCli = &http.Client{ + Timeout: 10 * time.Second, + } +} + +// HTTPClient returns an HTTP client configured for the test server. +func (h *Harness) HTTPClient() *http.Client { + if h.httpCli == nil { + h.t.Fatal("HTTP transport not configured") + } + return h.httpCli +} + +// getHTTPClientImpl returns the underlying HTTP client implementation. +func (h *Harness) getHTTPClientImpl() *httpcli.Client { + if h.httpSvr == nil || h.httpCli == nil { + h.t.Fatal("HTTP transport not configured") + } + u, err := url.Parse(h.httpSvr.URL) + if err != nil { + h.t.Fatalf("invalid test server URL: %v", err) + } + scheme := u.Scheme + host := u.Host + // Create WebSocket dialer for streaming endpoints + wsDialer := &websocket.Dialer{ + Proxy: http.ProxyFromEnvironment, + } + + return httpcli.NewClient( + scheme, + host, + h.httpCli, + goahttp.RequestEncoder, + goahttp.ResponseDecoder, + false, + wsDialer, + nil, + ) +} + +// HTTPClientEndpoints creates HTTP client endpoints for the service. +func (h *Harness) HTTPClientEndpoints() *withstreamservice.Endpoints { + c := h.getHTTPClientImpl() + return &withstreamservice.Endpoints{ + WithStreamMethod: c.WithStreamMethod(), + } +} + +// HTTPURL returns the base URL of the test HTTP server. +func (h *Harness) HTTPURL() string { + if h.httpSvr == nil { + h.t.Fatal("HTTP transport not configured") + } + return h.httpSvr.URL +} + +// HTTPRequest creates a new HTTP request for testing. +func (h *Harness) HTTPRequest(method, path string, body any) *http.Request { + h.t.Helper() + + url := h.HTTPURL() + path + + var bodyReader io.Reader + if body != nil { + data, err := json.Marshal(body) + if err != nil { + h.t.Fatalf("Failed to marshal request body: %v", err) + } + bodyReader = bytes.NewReader(data) + } + + req, err := http.NewRequestWithContext(h.ctx, method, url, bodyReader) + if err != nil { + h.t.Fatalf("Failed to create request: %v", err) + } + + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req +} + +// HTTPWSURL builds a websocket URL from the base HTTP URL and path. +func (h *Harness) HTTPWSURL(path string) string { + base := h.HTTPURL() + u, err := url.Parse(base) + if err != nil { + h.t.Fatalf("invalid base URL: %v", err) + } + if u.Scheme == "https" { + u.Scheme = "wss" + } else { + u.Scheme = "ws" + } + u.Path = path + return u.String() +} + +// HTTPDo performs an HTTP request and returns the response. +func (h *Harness) HTTPDo(req *http.Request) *http.Response { + h.t.Helper() + + resp, err := h.HTTPClient().Do(req) + if err != nil { + h.t.Fatalf("HTTP request failed: %v", err) + } + + return resp +} +` + +var WithoutStreamCode = `// setupHTTP initializes the HTTP test server and client. +func (h *Harness) setupHTTP() { + // Create endpoints + endpoints := withoutstreamservice.NewEndpoints(h.service) + + // Create HTTP handler + mux := goahttp.NewMuxer() + server := httpsvr.New(endpoints, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil) + httpsvr.Mount(mux, server) + + // Create test server + h.httpSvr = httptest.NewServer(mux) + + // Create HTTP client + h.httpCli = &http.Client{ + Timeout: 10 * time.Second, + } +} + +// HTTPClient returns an HTTP client configured for the test server. +func (h *Harness) HTTPClient() *http.Client { + if h.httpCli == nil { + h.t.Fatal("HTTP transport not configured") + } + return h.httpCli +} + +// getHTTPClientImpl returns the underlying HTTP client implementation. +func (h *Harness) getHTTPClientImpl() *httpcli.Client { + if h.httpSvr == nil || h.httpCli == nil { + h.t.Fatal("HTTP transport not configured") + } + u, err := url.Parse(h.httpSvr.URL) + if err != nil { + h.t.Fatalf("invalid test server URL: %v", err) + } + scheme := u.Scheme + host := u.Host + + return httpcli.NewClient( + scheme, + host, + h.httpCli, + goahttp.RequestEncoder, + goahttp.ResponseDecoder, + false, + ) +} + +// HTTPClientEndpoints creates HTTP client endpoints for the service. +func (h *Harness) HTTPClientEndpoints() *withoutstreamservice.Endpoints { + c := h.getHTTPClientImpl() + return &withoutstreamservice.Endpoints{ + WithoutStreamMethod: c.WithoutStreamMethod(), + } +} + +// HTTPURL returns the base URL of the test HTTP server. +func (h *Harness) HTTPURL() string { + if h.httpSvr == nil { + h.t.Fatal("HTTP transport not configured") + } + return h.httpSvr.URL +} + +// HTTPRequest creates a new HTTP request for testing. +func (h *Harness) HTTPRequest(method, path string, body any) *http.Request { + h.t.Helper() + + url := h.HTTPURL() + path + + var bodyReader io.Reader + if body != nil { + data, err := json.Marshal(body) + if err != nil { + h.t.Fatalf("Failed to marshal request body: %v", err) + } + bodyReader = bytes.NewReader(data) + } + + req, err := http.NewRequestWithContext(h.ctx, method, url, bodyReader) + if err != nil { + h.t.Fatalf("Failed to create request: %v", err) + } + + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req +} + +// HTTPDo performs an HTTP request and returns the response. +func (h *Harness) HTTPDo(req *http.Request) *http.Response { + h.t.Helper() + + resp, err := h.HTTPClient().Do(req) + if err != nil { + h.t.Fatalf("HTTP request failed: %v", err) + } + + return resp +} +` diff --git a/testing/codegen/testdata/dsls.go b/testing/codegen/testdata/dsls.go new file mode 100644 index 000000000..fb41be800 --- /dev/null +++ b/testing/codegen/testdata/dsls.go @@ -0,0 +1,26 @@ +package testdata + +import ( + . "goa.design/goa/v3/dsl" +) + +var WithStreamDSL = func() { + Service("WithStreamService", func() { + Method("WithStreamMethod", func() { + StreamingPayload(String) + HTTP(func() { + GET("/") + }) + }) + }) +} + +var WithoutStreamDSL = func() { + Service("WithoutStreamService", func() { + Method("WithoutStreamMethod", func() { + HTTP(func() { + GET("/") + }) + }) + }) +}