File: //opt/go/pkg/mod/github.com/go-openapi/
[email protected]/client/runtime_test.go
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"bytes"
"context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
token = "the-super-secret-token"
bearerToken = "Bearer " + token
charsetUTF8 = ";charset=utf-8"
)
// task This describes a task. Tasks require a content property to be set.
type task struct {
// Completed
Completed bool `json:"completed" xml:"completed"`
// Content Task content can contain [GFM](https://help.github.com/articles/github-flavored-markdown/).
Content string `json:"content" xml:"content"`
// ID This id property is autogenerated when a task is created.
ID int64 `json:"id" xml:"id"`
}
type testCtxKey uint8
const rtKey testCtxKey = 1
func TestRuntime_Concurrent(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
require.NoError(t, jsongen.Encode(result))
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
resCC := make(chan interface{})
errCC := make(chan error)
var res interface{}
for j := 0; j < 6; j++ {
go func() {
resC := make(chan interface{})
errC := make(chan error)
go func() {
var resp interface{}
var errp error
for i := 0; i < 3; i++ {
resp, errp = rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: http.MethodGet,
PathPattern: "/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res []task
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
})
<-time.After(100 * time.Millisecond)
}
resC <- resp
errC <- errp
}()
resCC <- <-resC
errCC <- <-errC
}()
}
c := 6
for c > 0 {
res = <-resCC
err = <-errCC
c--
}
require.NoError(t, err)
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
func TestRuntime_Canary(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
require.NoError(t, jsongen.Encode(result))
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: http.MethodGet,
PathPattern: "/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res []task
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
})
require.NoError(t, err)
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
type tasks struct {
Tasks []task `xml:"task"`
}
func TestRuntime_XMLCanary(t *testing.T) {
// test that it can make a simple XML request
// and get the response for it.
result := tasks{
Tasks: []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.XMLMime)
rw.WriteHeader(http.StatusOK)
xmlgen := xml.NewEncoder(rw)
require.NoError(t, xmlgen.Encode(result))
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: http.MethodGet,
PathPattern: "/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res tasks
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
})
require.NoError(t, err)
assert.IsType(t, tasks{}, res)
actual := res.(tasks)
assert.EqualValues(t, result, actual)
}
func TestRuntime_TextCanary(t *testing.T) {
// test that it can make a simple text request
// and get the response for it.
result := "1: task 1 content; 2: task 2 content"
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.TextMime)
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write([]byte(result))
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: http.MethodGet,
PathPattern: "/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res string
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
})
require.NoError(t, err)
assert.IsType(t, "", res)
actual := res.(string)
assert.EqualValues(t, result, actual)
}
func TestRuntime_CSVCanary(t *testing.T) {
// test that it can make a simple csv request
// and get the response for it.
result := `task,content,result
1,task1,ok
2,task2,fail
`
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.CSVMime)
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write([]byte(result))
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: http.MethodGet,
PathPattern: "/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res bytes.Buffer
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
})
require.NoError(t, err)
assert.IsType(t, bytes.Buffer{}, res)
actual := res.(bytes.Buffer)
assert.EqualValues(t, result, actual.String())
}
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return fn(req)
}
func TestRuntime_CustomTransport(t *testing.T) {
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
rt := New("localhost:3245", "/", []string{"ws", "wss", schemeHTTPS})
rt.Transport = roundTripperFunc(func(req *http.Request) (*http.Response, error) {
if req.URL.Scheme != schemeHTTPS {
return nil, errors.New("this was not a https request")
}
assert.Equal(t, "localhost:3245", req.Host)
assert.Equal(t, "localhost:3245", req.URL.Host)
var resp http.Response
resp.StatusCode = http.StatusOK
resp.Header = make(http.Header)
resp.Header.Set("content-type", "application/json")
buf := bytes.NewBuffer(nil)
enc := json.NewEncoder(buf)
require.NoError(t, enc.Encode(result))
resp.Body = io.NopCloser(buf)
return &resp, nil
})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: http.MethodGet,
PathPattern: "/",
Schemes: []string{"ws", "wss", schemeHTTPS},
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res []task
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
})
require.NoError(t, err)
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
func TestRuntime_CustomCookieJar(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
authenticated := false
for _, cookie := range req.Cookies() {
if cookie.Name == "sessionid" && cookie.Value == "abc" {
authenticated = true
}
}
if !authenticated {
username, password, ok := req.BasicAuth()
if ok && username == "username" && password == "password" {
authenticated = true
http.SetCookie(rw, &http.Cookie{Name: "sessionid", Value: "abc"})
}
}
if authenticated {
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
require.NoError(t, jsongen.Encode([]task{}))
} else {
rw.WriteHeader(http.StatusUnauthorized)
}
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
rt.Jar, err = cookiejar.New(nil)
require.NoError(t, err)
submit := func(authInfo runtime.ClientAuthInfoWriter) {
_, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: http.MethodGet,
PathPattern: "/",
Params: rwrtr,
AuthInfo: authInfo,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, _ runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
return map[string]interface{}{}, nil
}
return nil, errors.New("generic error")
}),
})
require.NoError(t, err)
}
submit(BasicAuth("username", "password"))
submit(nil)
}
func TestRuntime_AuthCanary(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get(runtime.HeaderAuthorization) != bearerToken {
rw.WriteHeader(http.StatusUnauthorized)
return
}
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
require.NoError(t, jsongen.Encode(result))
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res []task
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
AuthInfo: BearerToken(token),
})
require.NoError(t, err)
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
func TestRuntime_PickConsumer(t *testing.T) {
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("Content-Type") != "application/octet-stream" {
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+charsetUTF8)
rw.WriteHeader(http.StatusBadRequest)
return
}
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+charsetUTF8)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
_ = jsongen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return req.SetBodyParam(bytes.NewBufferString("hello"))
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "POST",
PathPattern: "/",
Schemes: []string{schemeHTTP},
ConsumesMediaTypes: []string{"application/octet-stream"},
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res []task
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
AuthInfo: BearerToken(token),
})
require.NoError(t, err)
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
func TestRuntime_ContentTypeCanary(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get(runtime.HeaderAuthorization) != bearerToken {
rw.WriteHeader(http.StatusBadRequest)
return
}
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+charsetUTF8)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
_ = jsongen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: http.MethodGet,
PathPattern: "/",
Schemes: []string{schemeHTTP},
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res []task
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
AuthInfo: BearerToken(token),
})
require.NoError(t, err)
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
func TestRuntime_ChunkedResponse(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get(runtime.HeaderAuthorization) != bearerToken {
rw.WriteHeader(http.StatusBadRequest)
return
}
rw.Header().Add(runtime.HeaderTransferEncoding, "chunked")
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+charsetUTF8)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
_ = jsongen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
// specDoc, err := spec.Load("../../fixtures/codegen/todolist.simple.yml")
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: http.MethodGet,
PathPattern: "/",
Schemes: []string{schemeHTTP},
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res []task
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
AuthInfo: BearerToken(token),
})
require.NoError(t, err)
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
func TestRuntime_DebugValue(t *testing.T) {
t.Run("empty DEBUG means Debug is False", func(t *testing.T) {
t.Setenv("DEBUG", "")
runtime := New("", "/", []string{schemeHTTPS})
assert.False(t, runtime.Debug)
})
t.Run("non-Empty DEBUG means Debug is True", func(t *testing.T) {
t.Run("with numerical value", func(t *testing.T) {
t.Setenv("DEBUG", "1")
runtime := New("", "/", []string{schemeHTTPS})
assert.True(t, runtime.Debug)
})
t.Run("with boolean value true", func(t *testing.T) {
t.Setenv("DEBUG", "true")
runtime := New("", "/", []string{schemeHTTPS})
assert.True(t, runtime.Debug)
})
t.Run("with boolean value false", func(t *testing.T) {
t.Setenv("DEBUG", "false")
runtime := New("", "/", []string{schemeHTTPS})
assert.False(t, runtime.Debug)
})
t.Run("with string value ", func(t *testing.T) {
t.Setenv("DEBUG", "foo")
runtime := New("", "/", []string{schemeHTTPS})
assert.True(t, runtime.Debug)
})
})
}
func TestRuntime_OverrideScheme(t *testing.T) {
runtime := New("", "/", []string{schemeHTTPS})
sch := runtime.pickScheme([]string{schemeHTTP})
assert.Equal(t, schemeHTTPS, sch)
}
func TestRuntime_OverrideClient(t *testing.T) {
client := &http.Client{}
runtime := NewWithClient("", "/", []string{schemeHTTPS}, client)
var i int
runtime.clientOnce.Do(func() { i++ })
assert.Equal(t, client, runtime.client)
assert.Equal(t, 0, i)
}
type overrideRoundTripper struct {
overridden bool
}
func (o *overrideRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) {
o.overridden = true
res := new(http.Response)
res.StatusCode = http.StatusOK
res.Body = io.NopCloser(bytes.NewBufferString("OK"))
return res, nil
}
func TestRuntime_OverrideClientOperation(t *testing.T) {
client := &http.Client{}
rt := NewWithClient("", "/", []string{schemeHTTPS}, client)
var i int
rt.clientOnce.Do(func() { i++ })
assert.Equal(t, client, rt.client)
assert.Equal(t, 0, i)
client2 := new(http.Client)
var transport = &overrideRoundTripper{}
client2.Transport = transport
require.NotEqual(t, client, client2)
_, err := rt.Submit(&runtime.ClientOperation{
Client: client2,
Params: runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
}),
Reader: runtime.ClientResponseReaderFunc(func(_ runtime.ClientResponse, _ runtime.Consumer) (interface{}, error) {
return map[string]interface{}{}, nil
}),
})
require.NoError(t, err)
assert.True(t, transport.overridden)
}
func TestRuntime_PreserveTrailingSlash(t *testing.T) {
var redirected bool
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+charsetUTF8)
if req.URL.Path == "/api/tasks" {
redirected = true
return
}
if req.URL.Path == "/api/tasks/" {
rw.WriteHeader(http.StatusOK)
}
}))
defer server.Close()
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
rwrtr := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
_, err = rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: http.MethodGet,
PathPattern: "/api/tasks/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, _ runtime.Consumer) (interface{}, error) {
if redirected {
return nil, errors.New("expected Submit to preserve trailing slashes - this caused a redirect")
}
if response.Code() == http.StatusOK {
return map[string]interface{}{}, nil
}
return nil, errors.New("generic error")
}),
})
require.NoError(t, err)
}
func TestRuntime_FallbackConsumer(t *testing.T) {
result := `W3siY29tcGxldGVkIjpmYWxzZSwiY29udGVudCI6ImRHRnpheUF4SUdOdmJuUmxiblE9IiwiaWQiOjF9XQ==`
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Add(runtime.HeaderContentType, "application/x-task")
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write([]byte(result))
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return req.SetBodyParam(bytes.NewBufferString("hello"))
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
// without the fallback consumer
_, err = rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "POST",
PathPattern: "/",
Schemes: []string{schemeHTTP},
ConsumesMediaTypes: []string{"application/octet-stream"},
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res []byte
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
})
require.Error(t, err)
assert.Equal(t, `no consumer: "application/x-task"`, err.Error())
// add the fallback consumer
rt.Consumers["*/*"] = rt.Consumers[runtime.DefaultMime]
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "POST",
PathPattern: "/",
Schemes: []string{schemeHTTP},
ConsumesMediaTypes: []string{"application/octet-stream"},
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res []byte
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
})
require.NoError(t, err)
assert.IsType(t, []byte{}, res)
actual := res.([]byte)
assert.EqualValues(t, result, actual)
}
func TestRuntime_AuthHeaderParamDetected(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get(runtime.HeaderAuthorization) != bearerToken {
rw.WriteHeader(http.StatusUnauthorized)
return
}
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
_ = jsongen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return req.SetHeaderParam(runtime.HeaderAuthorization, bearerToken)
})
hu, err := url.Parse(server.URL)
require.NoError(t, err)
rt := New(hu.Host, "/", []string{schemeHTTP})
rt.DefaultAuthentication = BearerToken("not-the-super-secret-token")
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == http.StatusOK {
var res []task
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
}
return nil, errors.New("generic error")
}),
})
require.NoError(t, err)
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
func TestRuntime_Timeout(t *testing.T) { //nolint:maintidx // linter evaluates the total lines of code, which is misleading
const (
operationID = "getTasks"
// these values should be sufficient for most CI engines
clientTimeout time.Duration = 25 * time.Millisecond
serverDelay time.Duration = 100 * time.Millisecond
clientNoTimeout time.Duration = 250 * time.Millisecond
ctxError = "context deadline exceeded"
)
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
signedContext := func(value string) context.Context {
return context.WithValue(context.Background(), rtKey, value)
}
requestWriter := func(timeout time.Duration) runtime.ClientRequestWriter {
// this writer sets the timeout parameter of the ClientRequest
return runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return req.SetTimeout(timeout)
})
}
requestReader := runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() != http.StatusOK {
return nil, errors.New("generic error")
}
var res []task
if e := consumer.Consume(response.Body(), &res); e != nil {
return nil, e
}
return res, nil
})
t.Run("with timeout specified as a request parameter, no operation context", func(t *testing.T) {
host, cleaner := serverBuilder(t, serverDelay, result)
t.Cleanup(cleaner)
rt := New(host, "/", []string{schemeHTTP})
rt.Context = signedContext("test")
rt.Transport = testContextTransport(t, true, true, "test")
t.Run("should not time out", func(t *testing.T) {
res, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: nil,
Params: requestWriter(clientNoTimeout),
Reader: requestReader,
})
require.NoError(t, err)
assertResult(result)(t, res)
})
t.Run("should time out", func(t *testing.T) {
_, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: nil,
Params: requestWriter(clientTimeout),
Reader: requestReader,
})
require.Error(t, err)
require.ErrorContains(t, err, ctxError)
})
})
t.Run("with timeout specified as a request parameter, no context at all", func(t *testing.T) {
host, cleaner := serverBuilder(t, serverDelay, result)
t.Cleanup(cleaner)
rt := New(host, "/", []string{schemeHTTP})
rt.Context = nil
rt.Transport = testContextTransport(t, true, false, "")
t.Run("should not time out", func(t *testing.T) {
res, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: nil,
Params: requestWriter(clientNoTimeout),
Reader: requestReader,
})
require.NoError(t, err)
assertResult(result)(t, res)
})
t.Run("should time out", func(t *testing.T) {
_, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: nil,
Params: requestWriter(clientTimeout),
Reader: requestReader,
})
require.Error(t, err)
require.ErrorContains(t, err, ctxError)
})
})
t.Run("with inherited operation context, timeout specified as operation context, request timeout set to 0", func(t *testing.T) {
host, cleaner := serverBuilder(t, serverDelay, result)
t.Cleanup(cleaner)
rt := New(host, "/", []string{schemeHTTP})
rt.Context = signedContext("test")
rt.Transport = testContextTransport(t, true, true, "test")
t.Run("should not time out", func(t *testing.T) {
operationCtx, cancel := context.WithTimeout(rt.Context, clientNoTimeout)
defer cancel()
res, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: operationCtx,
Params: requestWriter(0),
Reader: requestReader,
})
require.NoError(t, err)
assertResult(result)(t, res)
})
t.Run("should time out", func(t *testing.T) {
operationCtx, cancel := context.WithTimeout(rt.Context, clientTimeout)
defer cancel()
_, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: operationCtx,
Params: requestWriter(0),
Reader: requestReader,
})
require.Error(t, err)
require.ErrorContains(t, err, ctxError)
})
})
t.Run("with a fresh operation context, timeout specified as operation context, request timeout set to 0", func(t *testing.T) {
host, cleaner := serverBuilder(t, serverDelay, result)
t.Cleanup(cleaner)
rt := New(host, "/", []string{schemeHTTP})
rt.Context = nil
rt.Transport = testContextTransport(t, true, false, "")
t.Run("should not time out", func(t *testing.T) {
operationCtx, cancel := context.WithTimeout(context.Background(), clientNoTimeout)
defer cancel()
res, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: operationCtx,
Params: requestWriter(0),
Reader: requestReader,
})
require.NoError(t, err)
assertResult(result)(t, res)
})
t.Run("should time out", func(t *testing.T) {
operationCtx, cancel := context.WithTimeout(context.Background(), clientTimeout)
defer cancel()
_, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: operationCtx,
Params: requestWriter(0),
Reader: requestReader,
})
require.Error(t, err)
require.ErrorContains(t, err, ctxError)
})
})
t.Run("with an hypothetical timeout specified as runtime context, no operation context", func(t *testing.T) {
// in real life, the runtime context may be cancellable for other reasons than timeout
host, cleaner := serverBuilder(t, serverDelay, result)
t.Cleanup(cleaner)
t.Run("should not time out", func(t *testing.T) {
rt := New(host, "/", []string{schemeHTTP})
ctx, cancel := context.WithTimeout(signedContext("test"), clientNoTimeout)
defer cancel()
rt.Context = ctx
rt.Transport = testContextTransport(t, true, true, "test")
res, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: nil,
Params: requestWriter(0),
Reader: requestReader,
})
require.NoError(t, err)
assertResult(result)(t, res)
})
t.Run("should time out", func(t *testing.T) {
rt := New(host, "/", []string{schemeHTTP})
ctx, cancel := context.WithTimeout(signedContext("test"), clientTimeout)
defer cancel()
rt.Context = ctx
rt.Transport = testContextTransport(t, true, true, "test")
_, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: nil,
Params: requestWriter(0),
Reader: requestReader,
})
require.Error(t, err)
require.ErrorContains(t, err, ctxError)
})
})
t.Run("with multiple timeouts set, shortest wins", func(t *testing.T) {
host, cleaner := serverBuilder(t, serverDelay, result)
t.Cleanup(cleaner)
rt := New(host, "/", []string{schemeHTTP})
runtimeCtx, cancelRuntime := context.WithTimeout(signedContext("test"), clientNoTimeout)
rt.Context = runtimeCtx
defer cancelRuntime()
rt.Transport = testContextTransport(t, true, true, "test")
t.Run("should not time out", func(t *testing.T) {
operationCtx, cancelOperation := context.WithTimeout(
signedContext("test"),
serverDelay+(clientNoTimeout-serverDelay)/2,
)
defer cancelOperation()
res, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: operationCtx,
Params: requestWriter(serverDelay + (clientNoTimeout-serverDelay)/3),
Reader: requestReader,
})
require.NoError(t, err)
assertResult(result)(t, res)
})
t.Run("should time out on operation context deadline", func(t *testing.T) {
// NOTE: we'll be able to catch more precisely which context was canceled
// in go1.21 and context.WithTimeoutCause.
operationCtx, cancelOperation := context.WithTimeout(
signedContext("test"),
serverDelay-(clientNoTimeout-serverDelay)/4, // this one times out
)
defer cancelOperation()
_, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: operationCtx,
Params: requestWriter(
serverDelay + (clientNoTimeout-serverDelay)/4,
),
Reader: requestReader,
})
require.Error(t, err)
require.ErrorContains(t, err, ctxError)
})
t.Run("should time out on operation timeout param", func(t *testing.T) {
operationCtx, cancelOperation := context.WithTimeout(
signedContext("test"),
serverDelay+(clientNoTimeout-serverDelay)/2,
)
defer cancelOperation()
_, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: operationCtx,
Params: requestWriter(
serverDelay - (clientNoTimeout-serverDelay)/4, // this one times out
),
Reader: requestReader,
})
require.Error(t, err)
require.ErrorContains(t, err, ctxError)
})
})
t.Run("with no context, explicit infinite wait", func(t *testing.T) {
host, cleaner := serverBuilder(t, serverDelay, result)
t.Cleanup(cleaner)
rt := New(host, "/", []string{schemeHTTP})
rt.Context = signedContext("test")
rt.Transport = testContextTransport(t, false, true, "test") // verify that no deadline is passed to the emitted context
t.Run("should not time out", func(t *testing.T) {
resp, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: nil,
Params: requestWriter(0),
Reader: requestReader,
})
require.NoError(t, err)
assertResult(result)(t, resp)
})
})
t.Run("with no context, request uses the default timeout", func(t *testing.T) {
requestEmptyWriter := runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
host, cleaner := serverBuilder(t, serverDelay, result)
t.Cleanup(cleaner)
rt := New(host, "/", []string{schemeHTTP})
rt.Context = signedContext("test")
rt.Transport = testDefaultsInTransport(t, "test")
t.Run("should not time out", func(t *testing.T) {
resp, err := rt.Submit(&runtime.ClientOperation{
ID: operationID,
Context: nil,
Params: requestEmptyWriter, // leaves defaults
Reader: requestReader,
})
require.NoError(t, err)
assertResult(result)(t, resp)
})
})
}
func isContextSigned(ctx context.Context, value string) bool {
v, ok := ctx.Value(rtKey).(string)
return ok && v == value
}
func testContextTransport(t *testing.T, hasTimeout, expectSigned bool, value string) http.RoundTripper {
// inject a round tripper to check the context in the request about to be emitted
return roundTripperFunc(func(req *http.Request) (*http.Response, error) {
ctx := req.Context()
t.Run("request context should propagate value", func(t *testing.T) {
assert.Equal(t, expectSigned, isContextSigned(ctx, value), "expected the request context to inherit values")
})
t.Run(fmt.Sprintf("request context should have a deadline %t", hasTimeout), func(t *testing.T) {
_, hasDeadline := ctx.Deadline()
assert.Equalf(t, hasTimeout, hasDeadline, "expected request context to have a deadline")
})
return http.DefaultTransport.RoundTrip(req)
})
}
func testDefaultsInTransport(t *testing.T, value string) http.RoundTripper {
// inject a round tripper to check the context in the request about to be emitted
return roundTripperFunc(func(req *http.Request) (*http.Response, error) {
ctx := req.Context()
t.Run("request context should propagate value", func(t *testing.T) {
assert.True(t, isContextSigned(ctx, value), "expected the request context to inherit values")
})
t.Run("request context should have a default deadline", func(t *testing.T) {
deadline, hasDeadline := ctx.Deadline()
assert.True(t, hasDeadline, "expected request context to have a deadline")
remainingDuration := time.Until(deadline).Seconds()
assert.InDeltaf(t, DefaultTimeout.Seconds(), remainingDuration, 1.0, "expected timeout to be set to DefaultTimeout")
})
return http.DefaultTransport.RoundTrip(req)
})
}
func assertResult(result []task) func(testing.TB, interface{}) {
return func(t testing.TB, res interface{}) {
assert.IsType(t, []task{}, res)
actual, ok := res.([]task)
require.True(t, ok)
assert.EqualValues(t, result, actual)
}
}
func serverBuilder(t testing.TB, delay time.Duration, result []task) (string, func()) {
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context()
timer := time.NewTimer(delay)
select {
case <-ctx.Done():
http.Error(rw, ctx.Err().Error(), http.StatusInternalServerError)
return
case <-timer.C:
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
_ = jsongen.Encode(result)
return
}
}))
hu, err := url.Parse(server.URL)
require.NoError(t, err)
return hu.Host, server.Close
}