diff --git a/README.md b/README.md index 05ff5d17b..e318094de 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

-

Your API Mocking Tool for Agile Development

+

Mock APIs Across Protocols. Test Faster. Ship Better.

Github release @@ -17,124 +17,133 @@ Documentation

-# 🚀 Overview +## What is Mokapi? -Mokapi is an open-source tool that helps Agile, DevOps, and Continuous -Deployment teams design, test, and validate APIs before implementation. -It enables rapid prototyping of scenarios—like delayed responses, -failures, or edge cases—without needing a live backend. By simulating -real-world conditions early, Mokapi improves API quality and reduces -the risk of bugs in production. +Mokapi is an open-source API mocking tool that lets you develop and test without waiting +for backends. Mock REST APIs, Kafka topics, LDAP directories, and SMTP servers using +OpenAPI and AsyncAPI specifications. -# ✨ Features +Perfect for: +- Frontend developers building UIs before backends exist +- QA teams testing edge cases, errors, and timeouts +- DevOps engineers running reliable CI/CD tests without external dependencies +- API designers prototyping and validating contracts early -- **Multiple Protocol support**: HTTP, HTTPS, Apache Kafka, SMTP, LDAP -- **Everything as Code**: Reusing, version control, consistency and integrate mocks with your CI/CD. -- **An embedded JavaScript engine** to control everything - status, headers, delays, errors or other edge cases. -- **Patch Configuration** changes for mocking needs, rather than changing the original contract -- **Multiple Provider support**: File, HTTP, GIT, NPM to gather configurations and scripts. -- **Dashboard** to see what's going on. +## Quick Start -## 🧩 OpenAPI & AsyncAPI Support +Try Instantly -Mokapi supports mocking REST APIs using **OpenAPI 2.0 / 3.0 / 3.1** and event-driven systems using **AsyncAPI**. - -## 🔧 Spin Up Mokapi - -Install and start Mokapi using one of the following methods. -Replace the URL with your own OpenAPI or AsyncAPI specification. - -Windows -```shell -choco install mokapi -mokapi https://petstore31.swagger.io/api/v31/openapi.json ``` - -MacOS -```shell -brew tap marle3003/tap -brew install mokapi -mokapi https://petstore31.swagger.io/api/v31/openapi.json +npx go-mokapi https://petstore3.swagger.io/api/v3/openapi.json ``` -Docker -```shell -docker run -p 80:80 -p 8080:8080 mokapi/mokapi:latest https://petstore31.swagger.io/api/v31/openapi.json -``` +Then test your mock: -# 🎯 Hit Your First Mock -Once Mokapi is running, you can make requests to your mocked API like so: -```shell -curl http://localhost/api/v31/pet/2 -H 'Accept: application/json' ``` +curl http://localhost/api/v3/pet/1 -H 'Accept: application/json' +``` + +## Key Features -# 🧩 Customize Your Mock with JavaScript +### Multi-Protocol Support +Mock HTTP/HTTPS, Apache Kafka, LDAP, and SMTP — all from a single tool. -Mokapi makes it simple to control responses using embedded JavaScript. +### Specification-Driven +Uses OpenAPI and AsyncAPI specs as the source of truth. Your mocks stay aligned with your API contracts. -For example, you can dynamically change the response based on query parameters: +### Dynamic Behavior with JavaScript -```typescript -import { on } from 'mokapi'; +Control responses, simulate errors, add delays, or create complex workflows using embedded JavaScript: +```javascript +import { on } from 'mokapi' export default function() { on('http', (request, response) => { - switch (request.path.petId) { - case 2: - response.data.name = 'Betty'; - case 9: - response.statusCode = 404; + // Return 404 for specific IDs + if (request.path.petId === '999') { + response.statusCode = 404 + return } - }); + + // Customize response data + response.data.name = 'Custom Pet Name' + }) } ``` -# 🖥️ Dashboard +### Everything as Code +Version control your mocks alongside your code. Run them in CI/CD pipelines. No UI configuration required. -Mokapi’s dashboard lets you visualize your mock APIs. View requests and responses in real-time, generate and validate sample data for testing. +### Configuration Patching +Override parts of your OpenAPI spec without modifying the original file. Perfect for testing different scenarios. -Mokapi Web UI +### Built-in Dashboard +Visualize requests, responses, and logs in real-time at http://localhost:8080 +Mokapi Web UI -# 🧪 Learn by Example +### Multiple Providers +Load specs from local files, HTTP URLs, Git repositories, or NPM packages. -Explore tutorials that walk you through mocking different protocols and scenarios: +## Common Use Cases -- 🌍 [Get started with REST API](https://mokapi.io/resources/tutorials/get-started-with-rest-api)\ - This tutorial will show you how to mock a REST API using an OpenAPI specification. +### Frontend Development +Mock backend APIs while building UIs. Test loading states, errors, and edge cases without waiting for real endpoints. -- ⚡ [Mocking Kafka with AsyncAPI](https://mokapi.io/resources/tutorials/get-started-with-kafka)\ - Mocking a Kafka topic using Mokapi and verifying that a producer generates valid messages. +### API Testing +Simulate timeouts, 500 errors, rate limits, and malformed responses. Test how your application handles failures. -- 👨‍💻 [Mocking LDAP Authentication](https://mokapi.io/resources/tutorials/mock-ldap-authentication-in-node)\ - Simulate LDAP-based login flows, including group-based permissions. +### CI/CD Integration +Run fast, reliable tests without external dependencies. No flaky tests due to network issues or unavailable services. -- 📧 [Mocking SMTP Mail Servers](https://mokapi.io/resources/tutorials/mock-smtp-server-send-mail-using-node)\ - Use Mokapi to simulate sending and receiving emails in Node.js apps. +### Contract Validation +Validate that your requests and responses match your OpenAPI specification. Catch breaking changes early. -- 🖥️ [End-to-End Testing with Jest and GitHub Actions](https://mokapi.io/resources/tutorials/running-mokapi-in-a-ci-cd-pipeline)\ - Integrate Mokapi into your CI pipeline for full-stack E2E testing. +# Example Tutorials -> More examples are available on [mokapi.io/resources](https://mokapi.io/resources) +Explore tutorials that walk you through mocking different protocols and scenarios: + +- [Get started with REST API](https://mokapi.io/resources/tutorials/get-started-with-rest-api) + Mock a REST API using OpenAPI specification + +- [Mock Kafka with AsyncAPI](https://mokapi.io/resources/tutorials/get-started-with-kafka) + Simulate Kafka topics and validate message producers + +- [Mock LDAP Authentication](https://mokapi.io/resources/tutorials/mock-ldap-authentication-in-node)\ + Test authentication flows without a real LDAP server -# 📚 Documentation +- [Mock SMTP Mail Servers](https://mokapi.io/resources/tutorials/mock-smtp-server-send-mail-using-node)\ + Test email workflows without sending real messages -- [Get Started](https://mokapi.io/docs/welcome) -- [HTTP](https://mokapi.io/docs/http/overview) -- [Kafka](https://mokapi.io/docs/kafka/overview) -- [LDAP](https://mokapi.io/docs/ldap/overview) -- [SMTP](https://mokapi.io/docs/mail/overview) -- [Javascript API](https://mokapi.io/docs/javascript-api/overview) -- [Resources](https://mokapi.io/resources) +- [CI/CD Integration with GitHub Actions](https://mokapi.io/resources/tutorials/running-mokapi-in-a-ci-cd-pipeline)\ + Run Mokapi in automated test pipelines -# ☕ Support +> More examples [mokapi.io/resources](https://mokapi.io/resources) -Show your love for Mokapi and support the project by grabbing some cool merch! -[Visit the Mokapi Merchandise Store](https://mokapi.myspreadshop.ch) 🔥 +## Documentation -If you like Mokapi, consider buying me a coffee: +- [Getting Started Guide](https://mokapi.io/docs/welcome) +- [HTTP/REST API Documentation](https://mokapi.io/docs/http/overview) +- [Kafka Documentation](https://mokapi.io/docs/kafka/overview) +- [LDAP Documentation](https://mokapi.io/docs/ldap/overview) +- [SMTP/Mail Documentation](https://mokapi.io/docs/mail/overview) +- [JavaScript API Reference](https://mokapi.io/docs/javascript-api/overview) +- [Configuration Guide](https://mokapi.io/docs/configuration/overview) + +## Support the Project + +If Mokapi helps your team ship faster, consider supporting development: Buy Me A Coffee -# 📄 License +## License + +MIT License - see [LICENSE](https://github.com/marle3003/mokapi/blob/main/LICENSE) for details. + +## Links -This project is licensed under the MIT License. See the [LICENSE](https://github.com/marle3003/mokapi/blob/main/LICENSE) file for details. \ No newline at end of file +- Website: [mokapi.io](https://mokapi.io) +- GitHub: [github.com/marle3003/mokapi](https://github.com/marle3003/mokapi) +- NPM Package: [npmjs.com/package/go-mokapi](https://npmjs.com/package/go-mokapi) +- Documentation: [mokapi.io/docs](https://mokapi.io/docs) +- Tutorials: [mokapi.io/resources/tutorials](https://mokapi.io/resources/tutorials) +- Blog: [mokapi.io/resources/blogs](https://mokapi.io/resources/blogs) \ No newline at end of file diff --git a/api/handler_fileserver_test.go b/api/handler_fileserver_test.go index f554e48c1..323ee6feb 100644 --- a/api/handler_fileserver_test.go +++ b/api/handler_fileserver_test.go @@ -2,7 +2,6 @@ package api_test import ( "fmt" - "github.com/stretchr/testify/require" "mokapi/api" "mokapi/config/dynamic" "mokapi/config/static" @@ -14,6 +13,8 @@ import ( "net/url" "strings" "testing" + + "github.com/stretchr/testify/require" ) func TestHandler_FileServer(t *testing.T) { @@ -168,7 +169,7 @@ func TestOpenGraphInDashboard(t *testing.T) { app := runtime.New(cfg) app.AddHttp(&dynamic.Config{Info: dynamic.ConfigInfo{Url: mustParse("https://foo.bar")}, Data: openapitest.NewConfig("3.0", openapitest.WithInfo("Swagger Petstore", "1.0", "This is a sample server Petstore server."), - openapitest.WithPath("/pet/{petId}", openapitest.NewPath()), + openapitest.WithPath("/pet/{petId}"), )}, ) h := api.New(app, static.Api{Path: "/mokapi", Dashboard: true}) @@ -192,9 +193,9 @@ func TestOpenGraphInDashboard(t *testing.T) { app := runtime.New(cfg) app.AddHttp(&dynamic.Config{Info: dynamic.ConfigInfo{Url: mustParse("https://foo.bar")}, Data: openapitest.NewConfig("3.0", openapitest.WithInfo("Swagger Petstore", "1.0", "This is a sample server Petstore server."), - openapitest.WithPath("/pet/{petId}", openapitest.NewPath( + openapitest.WithPath("/pet/{petId}", openapitest.WithPathInfo("foo", "bar"), - )), + ), ), }) h := api.New(app, static.Api{Path: "/mokapi", Dashboard: true}) @@ -218,9 +219,9 @@ func TestOpenGraphInDashboard(t *testing.T) { app := runtime.New(cfg) app.AddHttp(&dynamic.Config{Info: dynamic.ConfigInfo{Url: mustParse("https://foo.bar")}, Data: openapitest.NewConfig("3.0", openapitest.WithInfo("Swagger Petstore", "1.0", "This is a sample server Petstore server."), - openapitest.WithPath("/pet/{petId}", openapitest.NewPath( + openapitest.WithPath("/pet/{petId}", openapitest.WithPathInfo("", "bar"), - ))), + )), }) h := api.New(app, static.Api{Path: "/mokapi", Dashboard: true}) try.Handler(t, @@ -243,9 +244,9 @@ func TestOpenGraphInDashboard(t *testing.T) { app := runtime.New(cfg) app.AddHttp(&dynamic.Config{Info: dynamic.ConfigInfo{Url: mustParse("https://foo.bar")}, Data: openapitest.NewConfig("3.0", openapitest.WithInfo("Swagger Petstore", "1.0", "This is a sample server Petstore server."), - openapitest.WithPath("/pet/{petId}", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation()), - ))), + openapitest.WithPath("/pet/{petId}", + openapitest.WithOperation("GET"), + )), }) h := api.New(app, static.Api{Path: "/mokapi", Dashboard: true}) try.Handler(t, @@ -268,12 +269,13 @@ func TestOpenGraphInDashboard(t *testing.T) { app := runtime.New(cfg) app.AddHttp(&dynamic.Config{Info: dynamic.ConfigInfo{Url: mustParse("https://foo.bar")}, Data: openapitest.NewConfig("3.0", openapitest.WithInfo("Swagger Petstore", "1.0", "This is a sample server Petstore server."), - openapitest.WithPath("/pet/{petId}", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation()), - )), - openapitest.WithPath("/pet/{petId}/foo", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation()), - ))), + openapitest.WithPath("/pet/{petId}", + openapitest.WithOperation("GET"), + ), + openapitest.WithPath("/pet/{petId}/foo", + openapitest.WithOperation("GET"), + ), + ), }) h := api.New(app, static.Api{Path: "/mokapi", Dashboard: true}) try.Handler(t, diff --git a/api/handler_http_test.go b/api/handler_http_test.go index 756af9fcb..460636508 100644 --- a/api/handler_http_test.go +++ b/api/handler_http_test.go @@ -111,10 +111,10 @@ func TestHandler_Http(t *testing.T) { return runtimetest.NewHttpApp( openapitest.NewConfig("3.0.0", openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("/foo/{bar}", openapitest.NewPath( + openapitest.WithPath("/foo/{bar}", openapitest.WithPathParam("bar", openapitest.WithParamSchema(schematest.New("string"))), - openapitest.WithOperation("get", openapitest.NewOperation()), - )), + openapitest.WithOperation("get"), + ), ), ) }, @@ -127,13 +127,13 @@ func TestHandler_Http(t *testing.T) { return runtimetest.NewHttpApp( openapitest.NewConfig("3.0.0", openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("/foo/{bar}", openapitest.NewPath( - openapitest.WithOperation("get", openapitest.NewOperation( + openapitest.WithPath("/foo/{bar}", + openapitest.WithOperation("get", openapitest.WithRequestBody("foo", true, openapitest.WithRequestContent("application/json", openapitest.NewContent(openapitest.WithSchema(schematest.New("string")))), ), - )), - ))), + ), + )), ) }, requestUrl: "http://foo.api/api/services/http/foo", @@ -145,11 +145,11 @@ func TestHandler_Http(t *testing.T) { return runtimetest.NewHttpApp( openapitest.NewConfig("3.0.0", openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation("get", openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation("get", openapitest.WithSecurity(map[string][]string{"foo": {}}), - )), - )), + ), + ), openapitest.WithComponentSecurity("foo", &openapi.ApiKeySecurityScheme{ Type: "apiKey", In: "header", @@ -168,9 +168,9 @@ func TestHandler_Http(t *testing.T) { openapitest.NewConfig("3.0.0", openapitest.WithGlobalSecurity(map[string][]string{"foo": {}}), openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation("get", openapitest.NewOperation()), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation("get"), + ), openapitest.WithComponentSecurity("foo", &openapi.ApiKeySecurityScheme{ Type: "apiKey", In: "header", @@ -188,20 +188,17 @@ func TestHandler_Http(t *testing.T) { return runtimetest.NewHttpApp( openapitest.NewConfig("3.0.0", openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("/foo/{bar}", openapitest.NewPath( - openapitest.WithOperation("get", openapitest.NewOperation( + openapitest.WithPath("/foo/{bar}", + openapitest.WithOperation("get", openapitest.WithResponse(http.StatusOK, openapitest.WithResponseDescription("foo description"), openapitest.WithContent( - "application/json", - openapitest.NewContent( - openapitest.WithSchema(schematest.New("string")), - ), - ), + "application/json", openapitest.WithSchema(schematest.New("string"))), openapitest.WithResponseHeader("foo", "bar", schematest.New("string")), ), - )), - ))), + ), + ), + ), ) }, requestUrl: "http://foo.api/api/services/http/foo", @@ -220,8 +217,8 @@ func TestHandler_Http(t *testing.T) { }, Value: openapitest.NewPath( openapitest.WithPathInfo("foo", "bar"), - openapitest.WithOperation("get", openapitest.NewOperation( - openapitest.WithResponseRef(http.StatusOK, + openapitest.WithOperation("get", + openapitest.UseResponseRef(http.StatusOK, &openapi.ResponseRef{ Reference: dynamic.Reference{ Ref: "#/components/pathItems/foo", @@ -229,16 +226,13 @@ func TestHandler_Http(t *testing.T) { }, Value: openapitest.NewResponse(openapitest.WithResponseDescription("foo description"), openapitest.WithContent( - "application/json", - openapitest.NewContent( - openapitest.WithSchema(schematest.New("string")), - ), + "application/json", openapitest.WithSchema(schematest.New("string")), ), openapitest.WithResponseHeader("foo", "bar", schematest.New("string")), ), }, ), - )), + ), ), }), ) @@ -254,19 +248,17 @@ func TestHandler_Http(t *testing.T) { return runtimetest.NewHttpApp( openapitest.NewConfig("3.0.0", openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("/foo/{bar}", openapitest.NewPath( - openapitest.WithOperation("get", openapitest.NewOperation( + openapitest.WithPath("/foo/{bar}", + openapitest.WithOperation("get", openapitest.WithResponse(http.StatusOK, openapitest.WithResponseDescription("foo description"), openapitest.WithContent( - "application/json", - openapitest.NewContent( - openapitest.WithSchema(schematest.New("string", schematest.And("number"))), - ), + "application/json", openapitest.WithSchema(schematest.New("string", schematest.And("number"))), ), ), - )), - ))), + ), + ), + ), ) }, requestUrl: "http://foo.api/api/services/http/foo", @@ -278,19 +270,16 @@ func TestHandler_Http(t *testing.T) { return runtimetest.NewHttpApp( openapitest.NewConfig("3.0.0", openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("/foo/{bar}", openapitest.NewPath( - openapitest.WithOperation("get", openapitest.NewOperation( + openapitest.WithPath("/foo/{bar}", + openapitest.WithOperation("get", openapitest.WithResponse(http.StatusOK, openapitest.WithResponseDescription("foo description"), openapitest.WithContent( - "application/json", - openapitest.NewContent( - openapitest.WithSchema(schematest.New("string", schematest.WithDefault("foobar"))), - ), + "application/json", openapitest.WithSchema(schematest.New("string", schematest.WithDefault("foobar"))), ), ), )), - ))), + ), ) }, requestUrl: "http://foo.api/api/services/http/foo", @@ -302,11 +291,11 @@ func TestHandler_Http(t *testing.T) { return runtimetest.NewHttpApp( openapitest.NewConfig("3.0.0", openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("/foo/{bar}", openapitest.NewPath( - openapitest.WithOperation("get", openapitest.NewOperation( + openapitest.WithPath("/foo/{bar}", + openapitest.WithOperation("get", openapitest.WithTagName("foo"), - )), - )), + ), + ), openapitest.WithTag("foo", "sum", "desc"), ), ) diff --git a/api/handler_schema_test.go b/api/handler_schema_test.go index 1a59d19bc..3f07355fb 100644 --- a/api/handler_schema_test.go +++ b/api/handler_schema_test.go @@ -35,16 +35,13 @@ func TestHandler_Schema_Example_Query(t *testing.T) { app: runtimetest.NewHttpApp(openapitest.NewConfig("3.1.0", openapitest.WithInfo("foo", "", ""), openapitest.WithPath("/foo", - openapitest.NewPath(openapitest.WithOperation("GET", - openapitest.NewOperation( - openapitest.WithResponse(200, - openapitest.WithContent("application/json", openapitest.NewContent( - openapitest.WithSchema(schematest.New("string")), - )), + openapitest.WithOperation("GET", + openapitest.WithResponse(200, + openapitest.WithContent("application/json", + openapitest.WithSchema(schematest.New("string")), ), ), ), - ), ), ), ), @@ -94,20 +91,15 @@ func TestHandler_Schema_Example_Query(t *testing.T) { app: runtimetest.NewHttpApp(openapitest.NewConfig("3.1.0", openapitest.WithInfo("foo", "", ""), openapitest.WithPath("/foo", - openapitest.NewPath(openapitest.WithOperation("GET", - openapitest.NewOperation( - openapitest.WithResponse(200, - openapitest.WithContent("application/json", openapitest.NewContent( - openapitest.WithSchema(schematest.New("string")), - )), - ), - openapitest.WithResponse(400, - openapitest.WithContent("application/json", openapitest.NewContent( - openapitest.WithSchema(schematest.New("string")), - )), + openapitest.WithOperation("GET", + openapitest.WithResponse(200, + openapitest.WithContent("application/json", + openapitest.WithSchema(schematest.New("string")), ), ), - ), + openapitest.WithResponse(400, + openapitest.WithContent("application/json", openapitest.WithSchema(schematest.New("string"))), + ), ), ), ), diff --git a/config/dynamic/asyncApi/parsing_test.go b/config/dynamic/asyncApi/parsing_test.go index e4dded816..ec2556f18 100644 --- a/config/dynamic/asyncApi/parsing_test.go +++ b/config/dynamic/asyncApi/parsing_test.go @@ -2,13 +2,14 @@ package asyncApi_test import ( "fmt" - "github.com/stretchr/testify/require" "mokapi/config/dynamic" "mokapi/config/dynamic/asyncApi" "mokapi/providers/asyncapi3" "mokapi/schema/json/schema" "net/url" "testing" + + "github.com/stretchr/testify/require" ) type readFunc func(cfg *dynamic.Config) error @@ -444,10 +445,8 @@ func TestSchema(t *testing.T) { t.Run("modify file reference direct", func(t *testing.T) { target := &schema.Schema{} message.Payload = &asyncapi3.SchemaRef{Value: &asyncapi3.MultiSchemaFormat{Schema: &schema.Schema{Ref: "foo.yml"}}} - var fooConfig *dynamic.Config reader := &testReader{readFunc: func(file *dynamic.Config) error { - file.Data = &schema.Schema{} - fooConfig = file + file.Data = target return nil }} @@ -455,10 +454,10 @@ func TestSchema(t *testing.T) { require.NoError(t, err) // modify - fooConfig.Data = target - err = fooConfig.Data.(dynamic.Parser).Parse(fooConfig, reader) + target = &schema.Schema{Description: "TARGET"} + err = config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) - require.Equal(t, target, message.Payload.Value.Schema.(*schema.Schema)) + require.Equal(t, "TARGET", message.Payload.Value.Schema.(*schema.Schema).Description) }) } diff --git a/config/dynamic/config.go b/config/dynamic/config.go index 29cd83296..64024fedf 100644 --- a/config/dynamic/config.go +++ b/config/dynamic/config.go @@ -47,6 +47,7 @@ type Config struct { Listeners Listeners Scope Scope SourceType SourceType + resolving map[string]bool } type Refs struct { @@ -229,3 +230,19 @@ func (c *Config) CloseScope() { func (e Event) String() string { return EventText[e] } + +func (c *Config) EnterRef(ref string) bool { + if c.resolving == nil { + c.resolving = map[string]bool{} + } + + if c.resolving[ref] { + return false + } + c.resolving[ref] = true + return true +} + +func (c *Config) LeaveRef(ref string) { + delete(c.resolving, ref) +} diff --git a/config/dynamic/resolve.go b/config/dynamic/resolve.go index 0c78e85b2..b15211f32 100644 --- a/config/dynamic/resolve.go +++ b/config/dynamic/resolve.go @@ -2,12 +2,13 @@ package dynamic import ( "fmt" - log "github.com/sirupsen/logrus" "mokapi/sortedmap" "net/url" "path/filepath" "reflect" "strings" + + log "github.com/sirupsen/logrus" ) type PathResolver interface { @@ -22,11 +23,14 @@ func Resolve(ref string, element interface{}, config *Config, reader Reader) err var err error fragment := ref[1:] + isLocal := true + parent := config if !strings.HasPrefix(ref, "#") { fragment, config, err = resolveResource(ref, element, config, reader) if err != nil { return fmt.Errorf("resolve reference '%v' failed: %w", ref, err) } + isLocal = false } err = resolveFragment(fragment, element, config, false) @@ -34,6 +38,29 @@ func Resolve(ref string, element interface{}, config *Config, reader Reader) err if err != nil { return fmt.Errorf("resolve reference '%v' failed: %w", ref, err) } + + // Parse the referenced schema again in the current context. + // This ensures nested $ref and $dynamicRef are resolved relative + // to the correct dynamic scope. + // element is **struct + p, ok := reflect.ValueOf(element).Elem().Interface().(Parser) + if ok { + if !isLocal { + // set parent scope hierarchy + config = &Config{Raw: config.Raw, Data: copyData(config.Data), Info: config.Info} + config.Scope.SetParent(parent.Scope) + } + if !config.EnterRef(ref) { + return nil + } + defer config.LeaveRef(ref) + + err = p.Parse(config, reader) + if err != nil { + return fmt.Errorf("resolve reference '%v' failed: %w", ref, err) + } + } + return nil } @@ -239,15 +266,6 @@ func resolveResource(ref string, element interface{}, config *Config, reader Rea sub, err := reader.Read(removeFragment(u), data) if err == nil { AddRef(config, sub) - if _, ok := sub.Data.(Parser); ok && len(sub.Raw) > 0 { - // parse again with parent scope hierarchy - sub = &Config{Raw: sub.Raw, Data: copyData(sub.Data), Info: sub.Info} - sub.Scope.SetParent(config.Scope) - err = Parse(sub, reader) - if err != nil { - return "", nil, err - } - } } return u.Fragment, sub, err } diff --git a/config/static/static_config.go b/config/static/static_config.go index 1960ffc72..a7b39a434 100644 --- a/config/static/static_config.go +++ b/config/static/static_config.go @@ -128,7 +128,7 @@ type HttpProvider struct { } type NpmProvider struct { - GlobalFolders []string `yaml:"globalFolders" name:"global-folders" explode:"global-folders"` + GlobalFolders []string `yaml:"globalFolders" name:"global-folders" explode:"global-folder"` Packages []NpmPackage `explode:"package"` } diff --git a/docs/get-started/installation.md b/docs/get-started/installation.md index f402e131e..fddd823b1 100644 --- a/docs/get-started/installation.md +++ b/docs/get-started/installation.md @@ -1,13 +1,41 @@ --- title: "Install Mokapi: Quick & Easy Setup Guide" -description: Learn how to install Mokapi effortlessly across Windows, macOS, and Linux. Follow the step-by-step guide for a smooth setup experience. +description: Install Mokapi on Windows, macOS, Linux, Docker, or Node.js. Follow a simple step-by-step guide to get started quickly. +subtitle: Get Mokapi running in seconds on Windows, macOS, Linux, Docker, or Node.js. +cards: + items: + - title: Run Your First Mock + href: /docs/get-started/running + description: Learn how to start Mokapi and mock your first API + - title: Explore Tutorials + href: /resources + description: Follow step-by-step guides for REST, Kafka, LDAP, and SMTP + - title: Write Scripts + href: /docs/javascript-api/overview + description: Add dynamic behavior to your mocks with JavaScript + - title: Configure Mokapi + href: /docs/configuration/overview + description: Customize ports, providers, and other settings --- # Install Mokapi -Mokapi is an open-source tool designed to simplify API mocking and schema validation. -It enables developers to prototype, test, and demonstrate APIs with realistic data and -scenarios. This guide provides straightforward instructions to install Mokapi on various -platforms. +## Overview + +Mokapi is an open-source API mocking tool that helps you develop and test faster by simulating REST APIs, Kafka topics, +LDAP directories, and SMTP servers. This guide shows you how to install Mokapi on your platform. + +Choose your preferred installation method based on your platform and workflow. + +```` box=benefits title="Try Without Installing" +Test Mokapi instantly with npx (requires Node.js): + +```bash style=simple +npx go-mokapi serve https://petstore3.swagger.io/api/v3/openapi.json +``` + + +This starts a mock server immediately without permanent installation. Perfect for quick tests and demos. +```` ## Installation Options @@ -16,10 +44,23 @@ Choose your preferred method below: ::: tabs +@tab "NPM" + +If you prefer to install Mokapi globally as a Node.js package, install [go-mokapi](https://www.npmjs.com/package/go-mokapi) +using: + +```bash +npm install -g go-mokapi +``` + +After installation, the mokapi command is available globally. + @tab "macOS" ### Homebrew +Install via Homebrew for easy updates and management: + ```bash brew tap marle3003/tap brew install mokapi @@ -27,12 +68,15 @@ brew install mokapi ### Direct Download -Download the latest macOS version from [GitHub](https://github.com/marle3003/mokapi/releases) +Download the latest macOS binary from [GitHub](https://github.com/marle3003/mokapi/releases). Extract +archive and move the binary to your PATH. @tab "Windows" ### Chocolatey +Install via Chocolatey for easy updates: + ```Powershell choco install mokapi ``` @@ -43,6 +87,8 @@ Download the latest Windows version from [GitHub](https://github.com/marle3003/m @tab "Linux" +Download the .deb package from the releases page and install: + ### Direct Download Download file appropriate for your Linux distribution and ARCH from the [release page](https://github.com/marle3003/mokapi/releases), then install with @@ -57,33 +103,54 @@ rpm -i mokapi_{version}_linux_{arch}.rpm @tab "Docker" -To get started with Mokapi using Docker, visit [DockerHub](https://hub.docker.com/r/mokapi/mokapi/tags) for a list of available images. -You can also use a custom base Docker image as demonstrated in [these examples](/resources/examples/mokapi-with-custom-base-image.md). +Mokapi provides official Docker images on [Docker Hub](https://hub.docker.com/r/mokapi/mokapi): ``` docker pull mokapi/mokapi ``` -@tab "NPM" +::: -If you prefer to install Mokapi as a Node.js package, use the following command: +```` box=info title="Verify Installation" +After installation, verify Mokapi is working: -```bash -npm install go-mokapi +```bash style=simple +mokapi --version ``` -::: +```` -### Mokapi Scripts Type Definitions +## TypeScript Support -Mokapi allows you to write **custom scripts** to handle API events or modify responses. -For full type safety and autocompletion in TypeScript, you can install the [`@types/mokapi`](https://www.npmjs.com/package/@types/mokapi`) package: +For full type safety and autocompletion when writing Mokapi scripts in TypeScript, install the type definitions: ```bash npm install --save-dev @types/mokapi ``` -## Next steps +This enables IntelliSense and type checking in your IDE when writing custom event handlers and scripts. + +### Example TypeScript Script + +```typescript title=petstore.ts +import { on } from 'mokapi' + +export default function() { + on('http', (request, response) => { + // TypeScript provides full autocompletion here + if (request.path.petId === '999') { + response.statusCode = 404 + response.data = { message: 'Pet not found' } + } + }) +} +``` +``` box=tip title="IDE Integration" +With @types/mokapi installed, editors like VS Code will provide autocompletion for Mokapi's API, making script development faster and less error-prone. +``` + +## What's Next? + +Now that Mokapi is installed, here's what you can do: -- [Create your first Mock](/docs/get-started/running.md) -- [Install @types/mokapi](https://www.npmjs.com/package/@types/mokapi) \ No newline at end of file +{{ card-grid key="cards" }} \ No newline at end of file diff --git a/docs/http/overview.md b/docs/http/overview.md index 8cf1c48ae..9b2068913 100644 --- a/docs/http/overview.md +++ b/docs/http/overview.md @@ -56,8 +56,8 @@ Point Mokapi to any publicly accessible OpenAPI specification: mokapi https://petstore3.swagger.io/api/v3/openapi.json ``` ```text box=tip -Mokapi supports both simplified syntax (`mokapi <url>`) and -verbose flags (`mokapi --providers-http-url <url>`). This guide uses the +Mokapi supports both simplified syntax (`mokapi `) and +verbose flags (`mokapi --providers-http-url `). This guide uses the simplified syntax for clarity. ``` diff --git a/imap/idle_test.go b/imap/idle_test.go index e7547bb36..4980c5eae 100644 --- a/imap/idle_test.go +++ b/imap/idle_test.go @@ -143,55 +143,6 @@ func TestIdle(t *testing.T) { require.NoError(t, err) require.Equal(t, "+ idling", res) - res, err = c.SendRaw("A01 FINISHED") - require.NoError(t, err) - require.Equal(t, "A01 BAD Expected DONE to end IDLE", res) - }, - }, - { - name: "send updates while idle", - handler: func(t *testing.T) imap.Handler { - return &imaptest.Handler{ - SelectFunc: func(mailbox string, readonly bool, session map[string]interface{}) (*imap.Selected, error) { - return &imap.Selected{}, nil - }, - IdleFunc: func(w imap.UpdateWriter, done chan struct{}, session map[string]interface{}) error { - session["idle"] = done - go func() { - err := w.WriteNumMessages(10) - require.NoError(t, err) - err = w.WriteMessageFlags(20, []imap.Flag{imap.FlagSeen}) - require.NoError(t, err) - err = w.WriteExpunge(1) - }() - return nil - }, - } - }, - test: func(t *testing.T, c *imap.Client) { - err := c.PlainAuth("", "bob", "password") - require.NoError(t, err) - _, err = c.Select("INBOX", false) - require.NoError(t, err) - - res, err := c.SendRaw("A01 IDLE") - require.NoError(t, err) - require.Equal(t, "+ idling", res) - - time.Sleep(4 * time.Second) - - res, err = c.ReadLine() - require.NoError(t, err) - require.Equal(t, "* 10 EXISTS", res) - - res, err = c.ReadLine() - require.NoError(t, err) - require.Equal(t, "* 20 FETCH (\\Seen)", res) - - res, err = c.ReadLine() - require.NoError(t, err) - require.Equal(t, "* 1 EXPUNGE", res) - res, err = c.SendRaw("A01 FINISHED") require.NoError(t, err) require.Equal(t, "A01 BAD Expected DONE to end IDLE", res) @@ -225,6 +176,69 @@ func TestIdle(t *testing.T) { } } +func TestSendUpdatesWhileIdle(t *testing.T) { + p := try.GetFreePort() + sent := make(chan bool) + s := &imap.Server{ + Addr: fmt.Sprintf(":%v", p), + Handler: &imaptest.Handler{ + SelectFunc: func(mailbox string, readonly bool, session map[string]interface{}) (*imap.Selected, error) { + return &imap.Selected{}, nil + }, + IdleFunc: func(w imap.UpdateWriter, done chan struct{}, session map[string]interface{}) error { + session["idle"] = done + go func() { + err := w.WriteNumMessages(10) + require.NoError(t, err) + err = w.WriteMessageFlags(20, []imap.Flag{imap.FlagSeen}) + require.NoError(t, err) + err = w.WriteExpunge(1) + sent <- true + }() + return nil + }, + }, + } + defer s.Close() + go func() { + err := s.ListenAndServe() + require.ErrorIs(t, err, imap.ErrServerClosed) + }() + + c := imap.NewClient(fmt.Sprintf("localhost:%v", p)) + defer func() { _ = c.Close() }() + + _, err := c.Dial() + require.NoError(t, err) + + err = c.PlainAuth("", "bob", "password") + require.NoError(t, err) + _, err = c.Select("INBOX", false) + require.NoError(t, err) + + res, err := c.SendRaw("A01 IDLE") + require.NoError(t, err) + require.Equal(t, "+ idling", res) + + <-sent + + res, err = c.ReadLine() + require.NoError(t, err) + require.Equal(t, "* 10 EXISTS", res) + + res, err = c.ReadLine() + require.NoError(t, err) + require.Equal(t, "* 20 FETCH (\\Seen)", res) + + res, err = c.ReadLine() + require.NoError(t, err) + require.Equal(t, "* 1 EXPUNGE", res) + + res, err = c.SendRaw("A01 FINISHED") + require.NoError(t, err) + require.Equal(t, "A01 BAD Expected DONE to end IDLE", res) +} + func TestIdle_DisconnectWithoutDone(t *testing.T) { var doneCh chan struct{} h := &imaptest.Handler{ diff --git a/npm/go-mokapi/README.md b/npm/go-mokapi/README.md index c825dc9cc..f10cdc2bc 100644 --- a/npm/go-mokapi/README.md +++ b/npm/go-mokapi/README.md @@ -45,22 +45,7 @@ mokapi https://petstore3.swagger.io/api/v3/openapi.json ### Other Installation Methods -Homebrew (macOS/Linux): -``` -brew tap marle3003/tap -brew install mokapi -``` - -Chocolatey (Windows): -```yaml -choco install mokapi -``` - -Docker: -``` -docker run -p 80:80 -p 8080:8080 mokapi/mokapi:latest \ - https://petstore3.swagger.io/api/v3/openapi.json -``` +Check other installation methods [here](https://mokapi.io/docs/get-started/installation) ## Key Features diff --git a/pkg/cli/bind.go b/pkg/cli/bind.go index 84ef0d8e4..c662e9f23 100644 --- a/pkg/cli/bind.go +++ b/pkg/cli/bind.go @@ -50,6 +50,9 @@ func (f *flagConfigBinder) setValue(ctx *bindContext) error { } err := ctx.setFieldFromStruct() if err != nil { + if len(ctx.paths) > 1 { + ctx.paths = []string{strings.Join(ctx.paths, "-")} + } if len(ctx.paths) == 1 { if arr, ok := ctx.value.([]string); ok { return f.explode(ctx.element, ctx.paths[0], arr) diff --git a/pkg/cmd/mokapi/flags/providers_npm_test.go b/pkg/cmd/mokapi/flags/providers_npm_test.go index 751423ca6..14b8f38b4 100644 --- a/pkg/cmd/mokapi/flags/providers_npm_test.go +++ b/pkg/cmd/mokapi/flags/providers_npm_test.go @@ -4,6 +4,7 @@ import ( "mokapi/config/static" "mokapi/pkg/cli" "mokapi/pkg/cmd/mokapi" + "os" "testing" "github.com/stretchr/testify/require" @@ -64,3 +65,25 @@ func TestRoot_Providers_Npm(t *testing.T) { }) } } + +func TestProviderNpm_Env(t *testing.T) { + key := "MOKAPI_PROVIDERS_NPM_GLOBAL_FOLDER" + err := os.Setenv(key, "/npm") + require.NoError(t, err) + t.Cleanup(func() { + _ = os.Unsetenv(key) + }) + + cmd := mokapi.NewCmdMokapi() + cmd.SetArgs([]string{}) + + cfg := static.NewConfig() + cmd.Run = func(cmd *cli.Command, args []string) error { + cfg = cmd.Config.(*static.Config) + return nil + } + err = cmd.Execute() + require.NoError(t, err) + + require.Equal(t, []string{"/npm"}, cfg.Providers.Npm.GlobalFolders) +} diff --git a/providers/openapi/components.go b/providers/openapi/components.go index a79698e6d..b45d1aeb0 100644 --- a/providers/openapi/components.go +++ b/providers/openapi/components.go @@ -19,32 +19,6 @@ type Components struct { type ComponentParameters map[string]*ParameterRef -func (c *Components) parse(config *dynamic.Config, reader dynamic.Reader) error { - if err := c.Schemas.Parse(config, reader); err != nil { - return fmt.Errorf("parse components failed: %w", err) - } - if err := c.Responses.parse(config, reader); err != nil { - return fmt.Errorf("parse components failed: %w", err) - } - if err := c.RequestBodies.parse(config, reader); err != nil { - return fmt.Errorf("parse components failed: %w", err) - } - if err := c.Parameters.parse(config, reader); err != nil { - return fmt.Errorf("parse components failed: %w", err) - } - if err := c.Examples.parse(config, reader); err != nil { - return fmt.Errorf("parse components failed: %w", err) - } - if err := c.Headers.parse(config, reader); err != nil { - return fmt.Errorf("parse components failed: %w", err) - } - if err := c.PathItems.parse(config, reader); err != nil { - return fmt.Errorf("parse components failed: %w", err) - } - - return nil -} - func (p ComponentParameters) parse(config *dynamic.Config, reader dynamic.Reader) error { for name, param := range p { if err := param.Parse(config, reader); err != nil { diff --git a/providers/openapi/components_test.go b/providers/openapi/components_test.go index 4e2b326f2..752353a41 100644 --- a/providers/openapi/components_test.go +++ b/providers/openapi/components_test.go @@ -3,16 +3,17 @@ package openapi_test import ( "encoding/json" "fmt" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/providers/openapi" "mokapi/providers/openapi/openapitest" - "mokapi/providers/openapi/schema" "mokapi/providers/openapi/schema/schematest" + "net/http" "net/url" "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestComponents_UnmarshalJSON(t *testing.T) { @@ -207,11 +208,27 @@ func TestComponents_Parse(t *testing.T) { return cfg, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentSchemaRef("foo", &schema.Schema{Ref: "foo.yml#/components/schemas/foo"}), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponse(http.StatusOK, + openapitest.WithContent("", + openapitest.WithSchemaRef("foo.yml#/components/schemas/foo"), + ), + ), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) - require.Equal(t, "string", config.Components.Schemas.Get("foo").Type.String()) + path := config.Paths["/foo"] + require.NotNil(t, path) + require.NotNil(t, path.Value) + require.NotNil(t, path.Value.Get) + require.NotNil(t, path.Value.Get.Responses) + res := path.Value.Get.Responses.GetResponse(http.StatusOK) + require.NotNil(t, res) + require.NotNil(t, res.Content[""]) + require.Equal(t, "string", res.Content[""].Schema.Type.String()) }, }, { @@ -221,10 +238,18 @@ func TestComponents_Parse(t *testing.T) { return nil, fmt.Errorf("TESTING ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentSchemaRef("foo", &schema.Schema{Ref: "foo.yml#/components/schemas/foo"}), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponse(http.StatusOK, + openapitest.WithContent("", + openapitest.WithSchemaRef("foo.yml#/components/schemas/foo"), + ), + ), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) - require.EqualError(t, err, "parse components failed: parse schema 'foo' failed: resolve reference 'foo.yml#/components/schemas/foo' failed: TESTING ERROR") + require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse response '200' failed: parse content '' failed: parse schema failed: resolve reference 'foo.yml#/components/schemas/foo' failed: TESTING ERROR") }, }, { @@ -240,12 +265,24 @@ func TestComponents_Parse(t *testing.T) { return cfg, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentResponseRef("foo", &openapi.ResponseRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/responses/foo"}}), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponseRef(http.StatusOK, + "foo.yml#/components/responses/foo", + ), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) - r, _ := config.Components.Responses["foo"] - require.Equal(t, "foo", r.Value.Description) + + path := config.Paths["/foo"] + require.NotNil(t, path) + require.NotNil(t, path.Value) + require.NotNil(t, path.Value.Get) + require.NotNil(t, path.Value.Get.Responses) + res := path.Value.Get.Responses.GetResponse(http.StatusOK) + require.Equal(t, "foo", res.Description) }, }, { @@ -255,10 +292,16 @@ func TestComponents_Parse(t *testing.T) { return nil, fmt.Errorf("TESTING ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentResponseRef("foo", &openapi.ResponseRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/schemas/foo"}}), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponseRef(http.StatusOK, + "foo.yml#/components/responses/foo", + ), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) - require.EqualError(t, err, "parse components failed: parse response 'foo' failed: resolve reference 'foo.yml#/components/schemas/foo' failed: TESTING ERROR") + require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse response '200' failed: resolve reference 'foo.yml#/components/responses/foo' failed: TESTING ERROR") }, }, { @@ -274,11 +317,20 @@ func TestComponents_Parse(t *testing.T) { return cfg, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentRequestBodyRef("foo", &openapi.RequestBodyRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/requestBodies/foo"}}), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithRequestBodyRef("foo.yml#/components/requestBodies/foo"), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) - require.Equal(t, "foo", config.Components.RequestBodies["foo"].Value.Description) + path := config.Paths["/foo"] + require.NotNil(t, path) + require.NotNil(t, path.Value) + require.NotNil(t, path.Value.Get) + require.NotNil(t, path.Value.Get.RequestBody.Value) + require.Equal(t, "foo", path.Value.Get.RequestBody.Value.Description) }, }, { @@ -288,10 +340,14 @@ func TestComponents_Parse(t *testing.T) { return nil, fmt.Errorf("TESTING ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentRequestBodyRef("foo", &openapi.RequestBodyRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/requestBodies/foo"}}), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithRequestBodyRef("foo.yml#/components/requestBodies/foo"), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) - require.EqualError(t, err, "parse components failed: parse request body 'foo' failed: resolve reference 'foo.yml#/components/requestBodies/foo' failed: TESTING ERROR") + require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse request body failed: resolve reference 'foo.yml#/components/requestBodies/foo' failed: TESTING ERROR") }, }, { @@ -307,11 +363,19 @@ func TestComponents_Parse(t *testing.T) { return cfg, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentParameterRef("foo", &openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/parameters/foo"}}), + openapitest.WithPath("/foo", + openapitest.WithPathParamRef("foo.yml#/components/parameters/foo"), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) - require.Equal(t, "foo", config.Components.Parameters["foo"].Value.Description) + path := config.Paths["/foo"] + require.NotNil(t, path) + require.NotNil(t, path.Value) + require.NotNil(t, path.Value.Parameters) + require.Len(t, path.Value.Parameters, 1) + require.NotNil(t, path.Value.Parameters[0].Value) + require.Equal(t, "foo", path.Value.Parameters[0].Value.Description) }, }, { @@ -321,10 +385,12 @@ func TestComponents_Parse(t *testing.T) { return nil, fmt.Errorf("TESTING ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentParameterRef("foo", &openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/parameters/foo"}}), + openapitest.WithPath("/foo", + openapitest.WithPathParamRef("foo.yml#/components/parameters/foo"), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) - require.EqualError(t, err, "parse components failed: parse parameter 'foo' failed: resolve reference 'foo.yml#/components/parameters/foo' failed: TESTING ERROR") + require.EqualError(t, err, "parse path '/foo' failed: parse parameter '0' failed: resolve reference 'foo.yml#/components/parameters/foo' failed: TESTING ERROR") }, }, { @@ -340,11 +406,26 @@ func TestComponents_Parse(t *testing.T) { return cfg, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentExampleRef("foo", &openapi.ExampleRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/examples/foo"}}), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponse(http.StatusOK, + openapitest.WithContent("", + openapitest.WithExampleRef("example", "foo.yml#/components/examples/foo")), + ), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) - require.Equal(t, "foo", config.Components.Examples["foo"].Value.Description) + path := config.Paths["/foo"] + require.NotNil(t, path) + require.NotNil(t, path.Value) + require.NotNil(t, path.Value.Get) + require.NotNil(t, path.Value.Get.Responses) + res := path.Value.Get.Responses.GetResponse(http.StatusOK) + require.NotNil(t, res) + require.NotNil(t, res.Content[""]) + require.Equal(t, "foo", res.Content[""].Examples["example"].Value.Description) }, }, { @@ -354,10 +435,18 @@ func TestComponents_Parse(t *testing.T) { return nil, fmt.Errorf("TESTING ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentExampleRef("foo", &openapi.ExampleRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/parameters/foo"}}), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponse(http.StatusOK, + openapitest.WithContent("application/json", + openapitest.WithExampleRef("foo", "foo.yml#/components/parameters/foo"), + ), + ), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) - require.EqualError(t, err, "parse components failed: parse example 'foo' failed: resolve reference 'foo.yml#/components/parameters/foo' failed: TESTING ERROR") + require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse response '200' failed: parse content 'application/json' failed: parse example 'foo' failed: resolve reference 'foo.yml#/components/parameters/foo' failed: TESTING ERROR") }, }, { @@ -373,11 +462,23 @@ func TestComponents_Parse(t *testing.T) { return cfg, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentHeaderRef("foo", &openapi.HeaderRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/headers/foo"}}), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponse(http.StatusOK, + openapitest.WithResponseHeaderRef("foo", "foo.yml#/components/headers/foo"), + ), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) - require.Equal(t, "foo", config.Components.Headers["foo"].Value.Description) + path := config.Paths["/foo"] + require.NotNil(t, path) + require.NotNil(t, path.Value) + require.NotNil(t, path.Value.Get) + require.NotNil(t, path.Value.Get.Responses) + res := path.Value.Get.Responses.GetResponse(http.StatusOK) + require.Equal(t, "foo", res.Headers["foo"].Value.Description) }, }, { @@ -387,10 +488,16 @@ func TestComponents_Parse(t *testing.T) { return nil, fmt.Errorf("TESTING ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithComponentHeaderRef("foo", &openapi.HeaderRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/headers/foo"}}), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponse(http.StatusOK, + openapitest.WithResponseHeaderRef("foo", "foo.yml#/components/headers/foo"), + ), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) - require.EqualError(t, err, "parse components failed: parse header 'foo' failed: resolve reference 'foo.yml#/components/headers/foo' failed: TESTING ERROR") + require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse response '200' failed: parse header 'foo' failed: resolve reference 'foo.yml#/components/headers/foo' failed: TESTING ERROR") }, }, } diff --git a/providers/openapi/config.go b/providers/openapi/config.go index 40b059258..fc9213bb9 100644 --- a/providers/openapi/config.go +++ b/providers/openapi/config.go @@ -114,15 +114,9 @@ func (c *Config) Parse(config *dynamic.Config, reader dynamic.Reader) error { return nil } - if err := c.Components.parse(config, reader); err != nil { - return err - } - - if err := c.Paths.parse(config, reader); err != nil { - return err - } + config.Scope.OpenIfNeeded(config.Info.Path()) - return nil + return c.Paths.parse(config, reader) } func (c *Config) Patch(patch *Config) { diff --git a/providers/openapi/config_schema_dynamic_test.go b/providers/openapi/config_schema_dynamic_test.go new file mode 100644 index 000000000..26696c62f --- /dev/null +++ b/providers/openapi/config_schema_dynamic_test.go @@ -0,0 +1,157 @@ +package openapi_test + +import ( + "mokapi/config/dynamic" + "mokapi/config/dynamic/dynamictest" + "mokapi/providers/openapi" + json "mokapi/schema/json/schema" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestConfig_DynamicSchema(t *testing.T) { + testdata := []struct { + name string + data string + test func(t *testing.T, c *openapi.Config) + }{ + { + name: "dynamic reference using $id", + data: ` +openapi: "3.0.0" +info: + title: "Dynamic Schema" +paths: + /foo: + get: + responses: + '200': + content: + 'application/json': + schema: + $id: /foo + $ref: '#/components/schemas/Response' + $defs: + content: + $dynamicAnchor: T + type: object + /bar: + get: + responses: + '200': + content: + 'application/json': + schema: + $id: /bar + $ref: '#/components/schemas/Response' + $defs: + content: + $dynamicAnchor: T + type: array +components: + schemas: + Response: + $defs: + content: + $dynamicAnchor: T + not: true + type: object + properties: + content: + $dynamicRef: '#T' + error: + type: object +`, + test: func(t *testing.T, c *openapi.Config) { + require.NotNil(t, c) + foo := c.Paths["/foo"].Value.Get.Responses.GetResponse(http.StatusOK).Content["application/json"].Schema + require.NotNil(t, foo) + require.NotNil(t, foo.Properties) + require.Equal(t, json.Types{"object"}, foo.Properties.Get("content").Type) + + bar := c.Paths["/bar"].Value.Get.Responses.GetResponse(http.StatusOK).Content["application/json"].Schema + require.NotNil(t, bar) + require.NotNil(t, bar.Properties) + require.Equal(t, json.Types{"array"}, bar.Properties.Get("content").Type) + + }, + }, + { + name: "dynamic reference without $id", + data: ` +openapi: "3.0.0" +info: + title: "Dynamic Schema" +paths: + /foo: + get: + responses: + '200': + content: + 'application/json': + schema: + $ref: '#/components/schemas/Response' + $defs: + content: + $dynamicAnchor: T + type: object + /bar: + get: + responses: + '200': + content: + 'application/json': + schema: + $id: /bar + $ref: '#/components/schemas/Response' + $defs: + content: + $dynamicAnchor: T + type: array +components: + schemas: + Response: + $defs: + content: + $dynamicAnchor: T + not: true + type: object + properties: + content: + $dynamicRef: '#T' + error: + type: object +`, + test: func(t *testing.T, c *openapi.Config) { + require.NotNil(t, c) + s := c.Paths["/foo"].Value.Get.Responses.GetResponse(http.StatusOK).Content["application/json"].Schema + require.NotNil(t, s) + require.NotNil(t, s.Properties) + require.Equal(t, json.Types{"object"}, s.Properties.Get("content").Type) + + bar := c.Paths["/bar"].Value.Get.Responses.GetResponse(http.StatusOK).Content["application/json"].Schema + require.NotNil(t, bar) + require.NotNil(t, bar.Properties) + require.Equal(t, json.Types{"array"}, bar.Properties.Get("content").Type) + }, + }, + } + + t.Parallel() + for _, tc := range testdata { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + c := &openapi.Config{} + err := yaml.Unmarshal([]byte(tc.data), c) + require.NoError(t, err) + err = c.Parse(&dynamic.Config{Data: c}, &dynamictest.Reader{}) + require.NoError(t, err) + tc.test(t, c) + }) + } +} diff --git a/providers/openapi/config_test.go b/providers/openapi/config_test.go index 25cd0d4c9..aa8b08765 100644 --- a/providers/openapi/config_test.go +++ b/providers/openapi/config_test.go @@ -2,8 +2,6 @@ package openapi_test import ( "fmt" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/media" @@ -13,6 +11,9 @@ import ( "net/http" "net/url" "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestResolve(t *testing.T) { @@ -431,7 +432,7 @@ func TestConfig_Patch(t *testing.T) { name: "patch add path on empty", configs: []*openapi.Config{ {}, - openapitest.NewConfig("1.0", openapitest.WithPath("/foo", openapitest.NewPath())), + openapitest.NewConfig("1.0", openapitest.WithPath("/foo")), }, test: func(t *testing.T, result *openapi.Config) { require.Len(t, result.Paths, 1) diff --git a/providers/openapi/content_test.go b/providers/openapi/content_test.go index e9fdd3a9e..db23d31bf 100644 --- a/providers/openapi/content_test.go +++ b/providers/openapi/content_test.go @@ -2,12 +2,13 @@ package openapi_test import ( "encoding/json" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" "mokapi/providers/openapi" "mokapi/providers/openapi/openapitest" "mokapi/providers/openapi/schema/schematest" "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestContent_UnmarshalJSON(t *testing.T) { @@ -162,20 +163,22 @@ func TestConfig_Patch_Content(t *testing.T) { { name: "add MediaType", configs: []*openapi.Config{ - openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{}}), + openapitest.NewConfig("1.0", + openapitest.WithPath( + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200), ), ), - ))), - openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{})), + ), + openapitest.NewConfig("1.0", + openapitest.WithPath( + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/plain")), ), ), - ))), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -187,20 +190,22 @@ func TestConfig_Patch_Content(t *testing.T) { { name: "append MediaType", configs: []*openapi.Config{ - openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{})), + openapitest.NewConfig("1.0", + openapitest.WithPath( + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/plain")), ), ), - ))), - openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{})), + ), + openapitest.NewConfig("1.0", + openapitest.WithPath( + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/html")), ), ), - ))), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -213,20 +218,26 @@ func TestConfig_Patch_Content(t *testing.T) { { name: "patch content", configs: []*openapi.Config{ - openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{})), + openapitest.NewConfig("1.0", + openapitest.WithPath( + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/plain")), ), ), - ))), - openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{Schema: schematest.New("string")})), + ), + openapitest.NewConfig("1.0", + openapitest.WithPath( + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, + openapitest.WithContent("text/plain", + openapitest.WithSchema(schematest.New("string")), + ), + ), ), ), - ))), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) diff --git a/providers/openapi/context_test.go b/providers/openapi/context_test.go index a6f8c27e3..b967b0fb2 100644 --- a/providers/openapi/context_test.go +++ b/providers/openapi/context_test.go @@ -1,13 +1,14 @@ package openapi_test import ( - "github.com/brianvoe/gofakeit/v6" - "github.com/stretchr/testify/require" "mokapi/media" "mokapi/providers/openapi" "mokapi/providers/openapi/openapitest" "net/http/httptest" "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/require" ) func TestContentTypeFromRequest(t *testing.T) { @@ -38,7 +39,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "", - response: openapitest.NewResponse(openapitest.WithContent("text/plain", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("text/plain")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -49,7 +50,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "text/plain", - response: openapitest.NewResponse(openapitest.WithContent("text/plain", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("text/plain")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -60,7 +61,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "*/*", - response: openapitest.NewResponse(openapitest.WithContent("text/plain", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("text/plain")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -71,7 +72,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "text/*", - response: openapitest.NewResponse(openapitest.WithContent("text/plain", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("text/plain")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -82,14 +83,14 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "image/*", - response: openapitest.NewResponse(openapitest.WithContent("text/plain", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("text/plain")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.EqualError(t, err, "none of requests content type(s) are supported: \"image/*\"") }, }, { accept: "image/*", - response: openapitest.NewResponse(openapitest.WithContent("image/png", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("image/png")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -100,7 +101,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "image/png", - response: openapitest.NewResponse(openapitest.WithContent("image/*", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("image/*")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -110,7 +111,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "image/*", - response: openapitest.NewResponse(openapitest.WithContent("image/*", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("image/*")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -120,7 +121,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "image/*", - response: openapitest.NewResponse(openapitest.WithContent("*/*", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("*/*")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -130,7 +131,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "*/*", - response: openapitest.NewResponse(openapitest.WithContent("*/*", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("*/*")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -141,7 +142,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "", - response: openapitest.NewResponse(openapitest.WithContent("*/*", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("*/*")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -152,7 +153,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "image/png", - response: openapitest.NewResponse(openapitest.WithContent("*/*", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("*/*")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -162,7 +163,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "application/json;odata=verbose", - response: openapitest.NewResponse(openapitest.WithContent("application/json", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("application/json")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) @@ -172,7 +173,7 @@ func TestContentTypeFromRequest(t *testing.T) { }, { accept: "application/json", - response: openapitest.NewResponse(openapitest.WithContent("application/json;odata=verbose", openapitest.NewContent())), + response: openapitest.NewResponse(openapitest.WithContent("application/json;odata=verbose")), validate: func(t *testing.T, ct media.ContentType, mt *openapi.MediaType, err error) { require.NotNil(t, ct) require.NotNil(t, mt) diff --git a/providers/openapi/event_test.go b/providers/openapi/event_test.go index d2797356f..18f957444 100644 --- a/providers/openapi/event_test.go +++ b/providers/openapi/event_test.go @@ -23,9 +23,9 @@ func TestEvent(t *testing.T) { { name: "use response example (deprecated)", config: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent( + openapitest.WithPath("/foo", + openapitest.WithOperation("GET", + openapitest.WithResponse(200, openapitest.UseContent( "application/json", &openapi.MediaType{ Schema: nil, Example: &openapi.ExampleValue{Value: "foo"}, @@ -34,7 +34,6 @@ func TestEvent(t *testing.T) { }, )), )), - )), ), test: func(t *testing.T, h openapi.Handler) { r := httptest.NewRequest("get", "http://localhost/foo", nil) @@ -43,16 +42,16 @@ func TestEvent(t *testing.T) { err := h.ServeHTTP(rr, r) require.Nil(t, err) - require.Equal(t, http.StatusOK, rr.Code) + require.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) require.Equal(t, `"foo"`, rr.Body.String()) }, }, { name: "use response examples", config: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent( + openapitest.WithPath("/foo", + openapitest.WithOperation("GET", + openapitest.WithResponse(200, openapitest.UseContent( "application/json", &openapi.MediaType{ Schema: nil, Example: nil, @@ -71,7 +70,6 @@ func TestEvent(t *testing.T) { }, )), )), - )), ), test: func(t *testing.T, h openapi.Handler) { r := httptest.NewRequest("get", "http://localhost/foo", nil) diff --git a/providers/openapi/example_test.go b/providers/openapi/example_test.go index ceeabe722..be4f329d9 100644 --- a/providers/openapi/example_test.go +++ b/providers/openapi/example_test.go @@ -3,8 +3,6 @@ package openapi_test import ( "encoding/json" "fmt" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/providers/openapi" @@ -12,6 +10,9 @@ import ( "net/http" "net/url" "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestExamples_UnmarshalJSON(t *testing.T) { @@ -154,13 +155,12 @@ func TestExample_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", + openapitest.UseContent("application/json", &openapi.MediaType{Examples: map[string]*openapi.ExampleRef{"foo": nil}}), ))), - )), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -173,13 +173,12 @@ func TestExample_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", + openapitest.UseContent("application/json", &openapi.MediaType{Examples: map[string]*openapi.ExampleRef{"foo": {}}}), ))), - )), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -192,14 +191,15 @@ func TestExample_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", &openapi.MediaType{ + openapitest.UseContent("application/json", &openapi.MediaType{ Examples: map[string]*openapi.ExampleRef{"foo": {Reference: dynamic.Reference{Ref: "foo.yml"}}}, }), - ))), - )), + ), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse response '200' failed: parse content 'application/json' failed: parse example 'foo' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -216,14 +216,15 @@ func TestExample_Parse(t *testing.T) { return cfg, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", &openapi.MediaType{ + openapitest.UseContent("application/json", &openapi.MediaType{ Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ExternalValue: "https://foo.bar"}}}, }), - ))), - )), + ), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -254,28 +255,28 @@ func TestConfig_Patch_Example(t *testing.T) { name: "patch Examples", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{}}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/plain", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{}}}, + })), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ - Summary: "foo summary", - Description: "foo description", - Value: "foo", - ExternalValue: "https://foo.bar", - }}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/plain", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ + Summary: "foo summary", + Description: "foo description", + Value: "foo", + ExternalValue: "https://foo.bar", + }}}, + })), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -291,28 +292,28 @@ func TestConfig_Patch_Example(t *testing.T) { name: "source Examples is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": nil}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/plain", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": nil}, + })), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ - Summary: "foo summary", - Description: "foo description", - Value: "foo", - ExternalValue: "https://foo.bar", - }}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/plain", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ + Summary: "foo summary", + Description: "foo description", + Value: "foo", + ExternalValue: "https://foo.bar", + }}}, + })), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -325,25 +326,25 @@ func TestConfig_Patch_Example(t *testing.T) { name: "source Examples value is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/plain", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {}}, + })), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ - Summary: "foo summary", - }}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/plain", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ + Summary: "foo summary", + }}}, + })), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -356,25 +357,25 @@ func TestConfig_Patch_Example(t *testing.T) { name: "patch Examples is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ - Summary: "foo summary", - }}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/plain", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ + Summary: "foo summary", + }}}, + })), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": nil}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/plain", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": nil}, + })), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -387,25 +388,25 @@ func TestConfig_Patch_Example(t *testing.T) { name: "patch Examples value is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ - Summary: "foo summary", - }}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/plain", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{ + Summary: "foo summary", + }}}, + })), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/plain", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {}}, + })), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) diff --git a/providers/openapi/handler_requestbody_test.go b/providers/openapi/handler_requestbody_test.go index 04a1ece39..d233a1183 100644 --- a/providers/openapi/handler_requestbody_test.go +++ b/providers/openapi/handler_requestbody_test.go @@ -29,15 +29,14 @@ func TestResponseHandler_ServeHTTP_ResponseBody(t *testing.T) { name: "text/plain", config: openapitest.NewConfig("3.0.0", openapitest.WithServer("http://localhost", ""), - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation("post", - openapitest.NewOperation( - openapitest.WithRequestBody("", false, - openapitest.WithRequestContent( - "text/plain", openapitest.NewContent(openapitest.WithSchema(schematest.New("string"))))), - openapitest.WithResponse(200), - )), - )), + openapitest.WithRequestBody("", false, + openapitest.WithRequestContent( + "text/plain", openapitest.NewContent(openapitest.WithSchema(schematest.New("string"))))), + openapitest.WithResponse(200), + ), + ), ), fn: func(t *testing.T, handler openapi.Handler) { r := httptest.NewRequest("post", "http://localhost/foo", strings.NewReader("foo")) @@ -57,15 +56,14 @@ func TestResponseHandler_ServeHTTP_ResponseBody(t *testing.T) { name: "text/*", config: openapitest.NewConfig("3.0.0", openapitest.WithServer("http://localhost", ""), - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation("post", - openapitest.NewOperation( - openapitest.WithRequestBody("", false, - openapitest.WithRequestContent( - "text/*", openapitest.NewContent(openapitest.WithSchema(schematest.New("string"))))), - openapitest.WithResponse(200), - )), - )), + openapitest.WithRequestBody("", false, + openapitest.WithRequestContent( + "text/*", openapitest.NewContent(openapitest.WithSchema(schematest.New("string"))))), + openapitest.WithResponse(200), + ), + ), ), fn: func(t *testing.T, handler openapi.Handler) { r := httptest.NewRequest("post", "http://localhost/foo", strings.NewReader("foo")) @@ -85,17 +83,16 @@ func TestResponseHandler_ServeHTTP_ResponseBody(t *testing.T) { name: "text/* > */*", config: openapitest.NewConfig("3.0.0", openapitest.WithServer("http://localhost", ""), - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation("post", - openapitest.NewOperation( - openapitest.WithRequestBody("", false, - openapitest.WithRequestContent( - "*/*", openapitest.NewContent(openapitest.WithSchema(schematest.New("number")))), - openapitest.WithRequestContent( - "text/*", openapitest.NewContent(openapitest.WithSchema(schematest.New("string"))))), - openapitest.WithResponse(200), - )), - )), + openapitest.WithRequestBody("", false, + openapitest.WithRequestContent( + "*/*", openapitest.NewContent(openapitest.WithSchema(schematest.New("number")))), + openapitest.WithRequestContent( + "text/*", openapitest.NewContent(openapitest.WithSchema(schematest.New("string"))))), + openapitest.WithResponse(200), + ), + ), ), fn: func(t *testing.T, handler openapi.Handler) { r := httptest.NewRequest("post", "http://localhost/foo", strings.NewReader("foo")) @@ -115,17 +112,16 @@ func TestResponseHandler_ServeHTTP_ResponseBody(t *testing.T) { name: "application/json free-form", config: openapitest.NewConfig("3.0.0", openapitest.WithServer("http://localhost", ""), - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation("post", - openapitest.NewOperation( - openapitest.WithRequestBody("", false, - openapitest.WithRequestContent( - "application/json", openapitest.NewContent(openapitest.WithSchema( - schematest.New("object"), - )))), - openapitest.WithResponse(200), - )), - )), + openapitest.WithRequestBody("", false, + openapitest.WithRequestContent( + "application/json", openapitest.NewContent(openapitest.WithSchema( + schematest.New("object"), + )))), + openapitest.WithResponse(200), + ), + ), ), fn: func(t *testing.T, handler openapi.Handler) { r := httptest.NewRequest("post", "http://localhost/foo", strings.NewReader(`{"foo": "abc","bar": 12}`)) @@ -145,17 +141,16 @@ func TestResponseHandler_ServeHTTP_ResponseBody(t *testing.T) { name: "application/json free-form", config: openapitest.NewConfig("3.0.0", openapitest.WithServer("http://localhost", ""), - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation("post", - openapitest.NewOperation( - openapitest.WithRequestBody("", false, - openapitest.WithRequestContent( - "application/json", openapitest.NewContent(openapitest.WithSchema( - schematest.New("object", schematest.WithProperty("foo", schematest.New("string"))), - )))), - openapitest.WithResponse(200), - )), - )), + openapitest.WithRequestBody("", false, + openapitest.WithRequestContent( + "application/json", openapitest.NewContent(openapitest.WithSchema( + schematest.New("object", schematest.WithProperty("foo", schematest.New("string"))), + )))), + openapitest.WithResponse(200), + ), + ), ), fn: func(t *testing.T, handler openapi.Handler) { r := httptest.NewRequest("post", "http://localhost/foo", strings.NewReader(`{"foo": "abc","bar": 12}`)) @@ -174,12 +169,11 @@ func TestResponseHandler_ServeHTTP_ResponseBody(t *testing.T) { name: "no request body specified but request contains body", config: openapitest.NewConfig("3.0.0", openapitest.WithServer("http://localhost", ""), - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation("post", - openapitest.NewOperation( - openapitest.WithResponse(200), - )), - )), + openapitest.WithResponse(200), + ), + ), ), fn: func(t *testing.T, handler openapi.Handler) { spy := &spyBody{Reader: bytes.NewBufferString(`{"foo": "abc","bar": 12}`)} diff --git a/providers/openapi/handler_response_test.go b/providers/openapi/handler_response_test.go index 2a7ea6e6f..ea9e9adf2 100644 --- a/providers/openapi/handler_response_test.go +++ b/providers/openapi/handler_response_test.go @@ -21,16 +21,12 @@ func TestHandler_Response(t *testing.T) { var opts []openapitest.ResponseOptions if contentType != "" { opts = append(opts, openapitest.WithContent(contentType, - openapitest.NewContent(openapitest.WithSchema(s))), + openapitest.WithSchema(s)), ) } - op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, opts...), - ) - return openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath(openapitest.WithOperation(http.MethodGet, op)))) + openapitest.WithPath("/foo", openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, opts...)))) } testcases := []struct { @@ -206,14 +202,15 @@ func TestHandler_Response_Context(t *testing.T) { { name: "use query data in random response", opt: openapitest.WithPath("/foo", - openapitest.NewPath(openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithOperation(http.MethodGet, openapitest.WithQueryParam("name", false, openapitest.WithParamSchema(schematest.New("string"))), openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", - openapitest.NewContent(openapitest.WithSchema( + openapitest.WithSchema( schematest.New("object", schematest.WithProperty("name", schematest.New("string")))), - ), - )), - )))), + ), + ), + ), + ), req: func() *http.Request { return httptest.NewRequest("get", "http://localhost/foo?name=foo", nil) }, @@ -225,15 +222,14 @@ func TestHandler_Response_Context(t *testing.T) { { name: "use from path parameter", opt: openapitest.WithPath("/users/{id}", - openapitest.NewPath( - openapitest.WithPathParam("id", openapitest.WithParamSchema(schematest.New("integer"))), - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", - openapitest.NewContent(openapitest.WithSchema( - schematest.New("object", schematest.WithProperty("id", schematest.New("integer")))), - ), - )), - ))), + openapitest.WithPathParam("id", openapitest.WithParamSchema(schematest.New("integer"))), + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", + openapitest.WithSchema( + schematest.New("object", schematest.WithProperty("id", schematest.New("integer")))), + ), + ), + ), ), req: func() *http.Request { return httptest.NewRequest("get", "http://localhost/users/123", nil) @@ -246,19 +242,18 @@ func TestHandler_Response_Context(t *testing.T) { { name: "parameter does not match response type", opt: openapitest.WithPath("/users/{id}", - openapitest.NewPath( - openapitest.WithPathParam("id", openapitest.WithParamSchema(schematest.New("integer"))), - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", - openapitest.NewContent(openapitest.WithSchema( - schematest.New("object", - schematest.WithProperty("id", schematest.New("string")), - schematest.WithRequired("id"), - ), - ), + openapitest.WithPathParam("id", openapitest.WithParamSchema(schematest.New("integer"))), + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", + openapitest.WithSchema( + schematest.New("object", + schematest.WithProperty("id", schematest.New("string")), + schematest.WithRequired("id"), ), - )), - ))), + ), + ), + ), + ), ), req: func() *http.Request { return httptest.NewRequest("get", "http://localhost/users/123", nil) @@ -271,15 +266,14 @@ func TestHandler_Response_Context(t *testing.T) { { name: "parameter does not match response type but string to int", opt: openapitest.WithPath("/users/{id}", - openapitest.NewPath( - openapitest.WithPathParam("id", openapitest.WithParamSchema(schematest.New("string"))), - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", - openapitest.NewContent(openapitest.WithSchema( - schematest.New("object", schematest.WithProperty("id", schematest.New("integer")))), - ), - )), - ))), + openapitest.WithPathParam("id", openapitest.WithParamSchema(schematest.New("string"))), + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", + openapitest.WithSchema( + schematest.New("object", schematest.WithProperty("id", schematest.New("integer")))), + ), + ), + ), ), req: func() *http.Request { return httptest.NewRequest("get", "http://localhost/users/123", nil) @@ -292,10 +286,9 @@ func TestHandler_Response_Context(t *testing.T) { { name: "response reference is nil", opt: openapitest.WithPath("/users", - openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( - openapitest.WithResponseRef(http.StatusOK, nil), - ))), + openapitest.WithOperation(http.MethodGet, + openapitest.UseResponse(http.StatusOK, nil), + ), ), req: func() *http.Request { return httptest.NewRequest("get", "http://localhost/users", nil) @@ -308,22 +301,22 @@ func TestHandler_Response_Context(t *testing.T) { { name: "from context and parameter is array ", opt: openapitest.WithPath("/foo", - openapitest.NewPath(openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithOperation(http.MethodGet, openapitest.WithQueryParam( "name", false, openapitest.WithParamSchema(schematest.New("array", schematest.WithItems("string"))), openapitest.WithExplode(true)), openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", - openapitest.NewContent( - openapitest.WithSchema( - schematest.New("array", schematest.WithItems( - "object", schematest.WithProperty("name", schematest.New("string"))), - ), + openapitest.WithSchema( + schematest.New("array", schematest.WithItems( + "object", schematest.WithProperty("name", schematest.New("string"))), ), ), ), - ))))), + ), + ), + ), req: func() *http.Request { return httptest.NewRequest("get", "http://localhost/foo?name=foo&name=bar", nil) }, diff --git a/providers/openapi/handler_security_test.go b/providers/openapi/handler_security_test.go index 5133a61f9..a1eeffd17 100644 --- a/providers/openapi/handler_security_test.go +++ b/providers/openapi/handler_security_test.go @@ -24,7 +24,7 @@ func TestHandler_Security(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, eh events.Handler) { op := openapitest.NewOperation( openapitest.WithSecurity(map[string][]string{"foo": {}}), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), ) c.Components.SecuritySchemes = map[string]openapi.SecurityScheme{ "foo": &openapi.HttpSecurityScheme{ @@ -32,7 +32,7 @@ func TestHandler_Security(t *testing.T) { }, } - openapitest.AppendPath("/foo", c, openapitest.WithOperation("GET", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("GET", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) r.Header.Set("Authorization", "Basic 123") rr := httptest.NewRecorder() @@ -56,7 +56,7 @@ func TestHandler_Security(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, eh events.Handler) { op := openapitest.NewOperation( openapitest.WithSecurity(map[string][]string{"foo": {}}), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), ) c.Components.SecuritySchemes = map[string]openapi.SecurityScheme{ "foo": &openapi.HttpSecurityScheme{ @@ -64,7 +64,7 @@ func TestHandler_Security(t *testing.T) { }, } - openapitest.AppendPath("/foo", c, openapitest.WithOperation("GET", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("GET", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -81,7 +81,7 @@ func TestHandler_Security(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, eh events.Handler) { op := openapitest.NewOperation( openapitest.WithSecurity(map[string][]string{"foo": {}}), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), ) c.Components.SecuritySchemes = map[string]openapi.SecurityScheme{ "foo": &openapi.HttpSecurityScheme{ @@ -89,7 +89,7 @@ func TestHandler_Security(t *testing.T) { }, } - openapitest.AppendPath("/foo", c, openapitest.WithOperation("GET", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("GET", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) r.Header.Set("Authorization", "Bearer 123") rr := httptest.NewRecorder() @@ -114,7 +114,7 @@ func TestHandler_Security(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, eh events.Handler) { op := openapitest.NewOperation( openapitest.WithSecurity(map[string][]string{"foo": {}}), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), ) c.Components.SecuritySchemes = map[string]openapi.SecurityScheme{ "foo": &openapi.ApiKeySecurityScheme{ @@ -123,7 +123,7 @@ func TestHandler_Security(t *testing.T) { }, } - openapitest.AppendPath("/foo", c, openapitest.WithOperation("GET", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("GET", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) r.Header.Set("X-API-KEY", "123") rr := httptest.NewRecorder() @@ -148,7 +148,7 @@ func TestHandler_Security(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, eh events.Handler) { op := openapitest.NewOperation( openapitest.WithSecurity(map[string][]string{"foo": {}}), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), ) c.Components.SecuritySchemes = map[string]openapi.SecurityScheme{ "foo": &openapi.ApiKeySecurityScheme{ @@ -157,7 +157,7 @@ func TestHandler_Security(t *testing.T) { }, } - openapitest.AppendPath("/foo", c, openapitest.WithOperation("GET", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("GET", op)) r := httptest.NewRequest("GET", "http://localhost/foo?apikey=123", nil) rr := httptest.NewRecorder() h(rr, r) @@ -182,7 +182,7 @@ func TestHandler_Security(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, eh events.Handler) { op := openapitest.NewOperation( openapitest.WithSecurity(map[string][]string{"foo": {}}), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), ) c.Components.SecuritySchemes = map[string]openapi.SecurityScheme{ "foo": &openapi.ApiKeySecurityScheme{ @@ -191,7 +191,7 @@ func TestHandler_Security(t *testing.T) { }, } - openapitest.AppendPath("/foo", c, openapitest.WithOperation("GET", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("GET", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) r.AddCookie(&http.Cookie{Name: "apikey", Value: "123"}) rr := httptest.NewRecorder() @@ -216,7 +216,7 @@ func TestHandler_Security(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, eh events.Handler) { op := openapitest.NewOperation( openapitest.WithSecurity(map[string][]string{"foo": {}}), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), ) c.Components.SecuritySchemes = map[string]openapi.SecurityScheme{ "foo": &openapi.NotSupportedSecurityScheme{ @@ -224,7 +224,7 @@ func TestHandler_Security(t *testing.T) { }, } - openapitest.AppendPath("/foo", c, openapitest.WithOperation("GET", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("GET", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -236,12 +236,12 @@ func TestHandler_Security(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, eh events.Handler) { op := openapitest.NewOperation( openapitest.WithSecurity(map[string][]string{"foo": {}}), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), ) c.Components.SecuritySchemes = map[string]openapi.SecurityScheme{ "foo": &openapi.OAuth2SecurityScheme{}, } - openapitest.AppendPath("/foo", c, openapitest.WithOperation("GET", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("GET", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) r.Header.Set("Authorization", "Bearer 123") @@ -273,7 +273,7 @@ func TestHandler_Security(t *testing.T) { "bar": {}, }, ), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), ) c.Components.SecuritySchemes = map[string]openapi.SecurityScheme{ "foo": &openapi.OAuth2SecurityScheme{}, @@ -282,7 +282,7 @@ func TestHandler_Security(t *testing.T) { Name: "apikey", }, } - openapitest.AppendPath("/foo", c, openapitest.WithOperation("GET", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("GET", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) r.Header.Set("Authorization", "Bearer 123") @@ -306,7 +306,7 @@ func TestHandler_Security(t *testing.T) { op := openapitest.NewOperation( openapitest.WithSecurity(map[string][]string{"foo": {}}), openapitest.WithSecurity(map[string][]string{"bar": {}}), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), ) c.Components.SecuritySchemes = map[string]openapi.SecurityScheme{ "foo": &openapi.OAuth2SecurityScheme{}, @@ -315,7 +315,7 @@ func TestHandler_Security(t *testing.T) { Name: "apikey", }, } - openapitest.AppendPath("/foo", c, openapitest.WithOperation("GET", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("GET", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) r.Header.Set("apikey", "API_KEY_123") diff --git a/providers/openapi/handler_test.go b/providers/openapi/handler_test.go index 94404738b..ae4b86161 100644 --- a/providers/openapi/handler_test.go +++ b/providers/openapi/handler_test.go @@ -41,8 +41,8 @@ func TestResolveEndpoint(t *testing.T) { name: "base path", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { c.Servers[0].Url = "http://localhost/root" - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/root/foo", nil) r = r.WithContext(context.WithValue(r.Context(), "servicePath", "/root")) rr := httptest.NewRecorder() @@ -55,8 +55,8 @@ func TestResolveEndpoint(t *testing.T) { name: "base path single slash", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { c.Servers[0].Url = "http://localhost/root" - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/root", nil) r = r.WithContext(context.WithValue(r.Context(), "servicePath", "/root")) rr := httptest.NewRecorder() @@ -69,8 +69,8 @@ func TestResolveEndpoint(t *testing.T) { name: "server url with slash suffix", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { c.Servers[0].Url = "http://localhost/root/" - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/root/foo", nil) r = r.WithContext(context.WithValue(r.Context(), "servicePath", "/root/")) rr := httptest.NewRecorder() @@ -86,11 +86,10 @@ func TestResolveEndpoint(t *testing.T) { op := openapitest.NewOperation( openapitest.WithOperationParam("bar", true), openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", - openapitest.NewContent()), + openapitest.WithContent("application/json"), ), ) - openapitest.AppendPath("/foo/{bar}", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo/{bar}", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/root/foo/bar", nil) r = r.WithContext(context.WithValue(r.Context(), "servicePath", "/root")) @@ -104,8 +103,8 @@ func TestResolveEndpoint(t *testing.T) { name: "spec define suffix / but request does not", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { c.Servers[0].Url = "http://localhost" - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo/", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo/", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -118,8 +117,8 @@ func TestResolveEndpoint(t *testing.T) { name: "spec define suffix no / but request does", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { c.Servers[0].Url = "http://localhost" - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo/", nil) rr := httptest.NewRecorder() h(rr, r) @@ -132,8 +131,8 @@ func TestResolveEndpoint(t *testing.T) { name: "both uses trailing slash", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { c.Servers[0].Url = "http://localhost" - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo/", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo/", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo/", nil) rr := httptest.NewRecorder() h(rr, r) @@ -158,8 +157,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "HTTP method does not match", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusNotFound, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusNotFound, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("POST", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -171,8 +170,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "no success response specified", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusNotFound, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusNotFound, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -184,7 +183,7 @@ func TestResolveEndpoint(t *testing.T) { name: "no response specified", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation() - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -196,8 +195,8 @@ func TestResolveEndpoint(t *testing.T) { name: "no success response specified but default", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { // 0 = default - op := openapitest.NewOperation(openapitest.WithResponse(0, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(0, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -207,8 +206,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "with endpoint", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -219,9 +218,9 @@ func TestResolveEndpoint(t *testing.T) { { name: "with multiple success response 1/2", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusNoContent, openapitest.WithContent("application/json", openapitest.NewContent())), - openapitest.WithResponse(http.StatusAccepted, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusNoContent, openapitest.WithContent("application/json")), + openapitest.WithResponse(http.StatusAccepted, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -231,9 +230,9 @@ func TestResolveEndpoint(t *testing.T) { { name: "with multiple success response 2/2", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusAccepted, openapitest.WithContent("application/json", openapitest.NewContent())), - openapitest.WithResponse(http.StatusNoContent, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusAccepted, openapitest.WithContent("application/json")), + openapitest.WithResponse(http.StatusNoContent, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -244,7 +243,7 @@ func TestResolveEndpoint(t *testing.T) { name: "empty response body", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation(openapitest.WithResponse(http.StatusNoContent)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -257,8 +256,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "POST request", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("POST", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("POST", op)) r := httptest.NewRequest("POST", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -268,11 +267,11 @@ func TestResolveEndpoint(t *testing.T) { { name: "POST request invalid data", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithRequestBody("", true, openapitest.WithRequestContent("application/json", openapitest.NewContent(openapitest.WithSchema(schematest.New("string", schematest.WithMinLength(4))))))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("POST", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("POST", op)) r := httptest.NewRequest("POST", "http://localhost/foo", strings.NewReader(`"foo"`)) r.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -287,8 +286,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "PUT request", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("PUT", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("PUT", op)) r := httptest.NewRequest("PUT", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -301,8 +300,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "PATCH request", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("PATCH", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("PATCH", op)) r := httptest.NewRequest("PATCH", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -315,8 +314,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "DELETE request", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("DELETE", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("DELETE", op)) r := httptest.NewRequest("DELETE", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -329,8 +328,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "HEAD request", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("HEAD", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("HEAD", op)) r := httptest.NewRequest("HEAD", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -343,8 +342,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "OPTIONS request", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("OPTIONS", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("OPTIONS", op)) r := httptest.NewRequest("OPTIONS", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -357,8 +356,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "TRACE request", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("TRACE", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("TRACE", op)) r := httptest.NewRequest("TRACE", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -371,8 +370,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "QUERY request", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("QUERY", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("QUERY", op)) r := httptest.NewRequest("QUERY", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -385,8 +384,8 @@ func TestResolveEndpoint(t *testing.T) { { name: "custom request method", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { - op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("LINK", op)) + op := openapitest.NewOperation(openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("LINK", op)) r := httptest.NewRequest("LINK", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -402,7 +401,7 @@ func TestResolveEndpoint(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK), openapitest.WithOperationParam("id", false)) - openapitest.AppendPath("/foo/{id}", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo/{id}", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -416,7 +415,7 @@ func TestResolveEndpoint(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK), openapitest.WithOperationParam("id", false)) - openapitest.AppendPath("/foo/{id}/bar", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo/{id}/bar", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -428,9 +427,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with path parameter present", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithOperationParam("id", false)) - openapitest.AppendPath("/foo/{id}", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo/{id}", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo/42", nil) rr := httptest.NewRecorder() h(rr, r) @@ -443,7 +442,7 @@ func TestResolveEndpoint(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK), openapitest.WithOperationParam("id", false)) - openapitest.AppendPath("/foo/bar", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo/bar", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo/bar", nil) rr := httptest.NewRecorder() h(rr, r) @@ -458,9 +457,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with optional query parameter and not present", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithQueryParam("id", false)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -473,7 +472,7 @@ func TestResolveEndpoint(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK), openapitest.WithQueryParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -485,9 +484,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with required query parameter and present", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithQueryParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo?id=42", nil) rr := httptest.NewRecorder() h(rr, r) @@ -501,9 +500,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with optional query parameter and not present", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithCookieParam("id", false)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -516,7 +515,7 @@ func TestResolveEndpoint(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK), openapitest.WithCookieParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -528,9 +527,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with required query parameter and present", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithCookieParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.AddCookie(&http.Cookie{Name: "id", Value: "42"}) rr := httptest.NewRecorder() @@ -545,9 +544,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with optional query parameter and not present", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithHeaderParam("id", false)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -560,7 +559,7 @@ func TestResolveEndpoint(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK), openapitest.WithHeaderParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) rr := httptest.NewRecorder() h(rr, r) @@ -572,9 +571,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with required query parameter and present", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithHeaderParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("id", "42") rr := httptest.NewRecorder() @@ -589,9 +588,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with content-type", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithHeaderParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("id", "42") r.Header.Set("accept", "application/json") @@ -605,9 +604,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with content-type extensions", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json;odata=verbose", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json;odata=verbose")), openapitest.WithHeaderParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("id", "42") r.Header.Set("accept", "application/json;odata=verbose") @@ -621,9 +620,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with content-type extensions", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithHeaderParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("id", "42") r.Header.Set("accept", "application/json;odata=verbose") @@ -637,9 +636,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with content-type extensions exactly", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json;odata=verbose", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json;odata=verbose")), openapitest.WithHeaderParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("id", "42") r.Header.Set("accept", "application/json;odata=verbose") @@ -653,9 +652,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with content-type multiple accepted", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithHeaderParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("id", "42") r.Header.Set("accept", "text/plain,application/json") @@ -669,9 +668,9 @@ func TestResolveEndpoint(t *testing.T) { name: "with content-type not supported", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("text/plain", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("text/plain")), openapitest.WithHeaderParam("id", true)) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("id", "42") r.Header.Set("accept", "application/json") @@ -692,9 +691,9 @@ func TestResolveEndpoint(t *testing.T) { openapitest.WithOperationParam("petId", true, openapitest.WithParamSchema(schematest.New("integer")))) find := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK), - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/pet/{petId}", c, openapitest.WithOperation("get", byId)) - openapitest.AppendPath("/pet/findByStatus", c, openapitest.WithOperation("get", find)) + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/pet/{petId}", c, openapitest.UseOperation("get", byId)) + openapitest.AppendPath("/pet/findByStatus", c, openapitest.UseOperation("get", find)) r := httptest.NewRequest("get", "http://localhost/pet/findByStatus", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -711,21 +710,21 @@ func TestResolveEndpoint(t *testing.T) { byName := openapitest.NewOperation( openapitest.WithOperationParam("name", true, openapitest.WithParamSchema(schematest.New("string"))), openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent( + openapitest.WithContent("application/json", openapitest.WithSchema(schematest.New("string", schematest.WithConst("Name"))), - )), + ), ), ) find := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent())), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json")), openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent( + openapitest.WithContent("application/json", openapitest.WithSchema(schematest.New("string", schematest.WithConst("findByStatus"))), - )), + ), ), ) - openapitest.AppendPath("/pet/{name}", c, openapitest.WithOperation("get", byName)) - openapitest.AppendPath("/pet/findByStatus", c, openapitest.WithOperation("get", find)) + openapitest.AppendPath("/pet/{name}", c, openapitest.UseOperation("get", byName)) + openapitest.AppendPath("/pet/findByStatus", c, openapitest.UseOperation("get", find)) r := httptest.NewRequest("get", "http://localhost/pet/findByStatus", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -776,8 +775,8 @@ func TestHandler_Event(t *testing.T) { name: "no response found", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -795,8 +794,8 @@ func TestHandler_Event(t *testing.T) { name: "event sets unknown status code", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -815,9 +814,9 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), - openapitest.WithContent("text/plain", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.WithContent("application/json"), + openapitest.WithContent("text/plain"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -839,9 +838,9 @@ func TestHandler_Event(t *testing.T) { openapitest.WithRequestBody("", true, openapitest.WithRequestContent("application/json", openapitest.NewContent())), openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), )) - openapitest.AppendPath("/foo", c, openapitest.WithOperation(http.MethodPost, op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation(http.MethodPost, op)) r := httptest.NewRequest("post", "http://localhost/foo", strings.NewReader(`{ "foo": "bar" }`)) r.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -861,9 +860,9 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), )) - openapitest.AppendPath("/foo", c, openapitest.WithOperation(http.MethodPost, op)) + openapitest.AppendPath("/foo", c, openapitest.UseOperation(http.MethodPost, op)) r := httptest.NewRequest("post", "http://localhost/foo", strings.NewReader(`{ "foo": "bar" }`)) r.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -883,10 +882,10 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), )) openapitest.AppendPath("/foo/{id}", c, - openapitest.WithOperation(http.MethodPost, op), + openapitest.UseOperation(http.MethodPost, op), openapitest.WithPathParam("id", openapitest.WithParamSchema(schematest.New("string"))), ) r := httptest.NewRequest("post", "http://localhost/foo/123", strings.NewReader(`{ "foo": "bar" }`)) @@ -908,10 +907,10 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), )) openapitest.AppendPath("/foo/{id}/", c, - openapitest.WithOperation(http.MethodPost, op), + openapitest.UseOperation(http.MethodPost, op), openapitest.WithPathParam("id", openapitest.WithParamSchema(schematest.New("string"))), ) r := httptest.NewRequest("post", "http://localhost/foo/123", strings.NewReader(`{ "foo": "bar" }`)) @@ -933,10 +932,10 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), )) openapitest.AppendPath("/foo/{id}/", c, - openapitest.WithOperation(http.MethodPost, op), + openapitest.UseOperation(http.MethodPost, op), openapitest.WithPathParam("id", openapitest.WithParamSchema(schematest.New("string"))), ) r := httptest.NewRequest("post", "http://localhost/foo/123", strings.NewReader(`{ "foo": "bar" }`)) @@ -957,10 +956,10 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), )) openapitest.AppendPath("/foo/{id}/", c, - openapitest.WithOperation(http.MethodPost, op), + openapitest.UseOperation(http.MethodPost, op), openapitest.WithPathParam("id", openapitest.WithParamSchema(schematest.New("string"))), ) r := httptest.NewRequest("post", "http://localhost/foo/123", strings.NewReader(`{ "foo": "bar" }`)) @@ -987,14 +986,14 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), ), openapitest.WithOperationParam("id", true, openapitest.WithParamSchema(schematest.New("integer"))), openapitest.WithOperationId("foo-operation"), ) openapitest.AppendPath("/foo/{id}", c, - openapitest.WithOperation(http.MethodGet, op), + openapitest.UseOperation(http.MethodGet, op), ) r := httptest.NewRequest(http.MethodGet, "http://localhost/foo/123", nil) rr := httptest.NewRecorder() @@ -1035,12 +1034,12 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), - openapitest.WithContent("text/plain", openapitest.NewContent()), + openapitest.WithContent("application/json"), + openapitest.WithContent("text/plain"), ), ) openapitest.AppendPath("/foo", c, - openapitest.WithOperation(http.MethodGet, op), + openapitest.UseOperation(http.MethodGet, op), ) r := httptest.NewRequest(http.MethodGet, "http://localhost/foo", nil) rr := httptest.NewRecorder() @@ -1059,11 +1058,11 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), ), ) openapitest.AppendPath("/foo", c, - openapitest.WithOperation(http.MethodGet, op), + openapitest.UseOperation(http.MethodGet, op), ) r := httptest.NewRequest(http.MethodGet, "http://localhost/foo", nil) rr := httptest.NewRecorder() @@ -1082,11 +1081,11 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), ), ) openapitest.AppendPath("/foo", c, - openapitest.WithOperation(http.MethodGet, op), + openapitest.UseOperation(http.MethodGet, op), ) r := httptest.NewRequest(http.MethodGet, "http://localhost/foo", nil) rr := httptest.NewRecorder() @@ -1105,12 +1104,12 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), openapitest.WithResponseHeader("foo", "", schematest.New("array")), ), ) openapitest.AppendPath("/foo", c, - openapitest.WithOperation(http.MethodGet, op), + openapitest.UseOperation(http.MethodGet, op), ) r := httptest.NewRequest(http.MethodGet, "http://localhost/foo", nil) rr := httptest.NewRecorder() @@ -1129,12 +1128,12 @@ func TestHandler_Event(t *testing.T) { test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, sm *events.StoreManager) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), openapitest.WithResponseHeader("foo", "", schematest.New("array", schematest.WithItems("integer"))), ), ) openapitest.AppendPath("/foo", c, - openapitest.WithOperation(http.MethodGet, op), + openapitest.UseOperation(http.MethodGet, op), ) r := httptest.NewRequest(http.MethodGet, "http://localhost/foo", nil) rr := httptest.NewRecorder() @@ -1193,8 +1192,8 @@ func TestHandler_Log(t *testing.T) { name: "simple", test: func(t *testing.T, h http.HandlerFunc, c *openapi.Config, eh events.Handler) { op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", op)) + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", c, openapitest.UseOperation("get", op)) r := httptest.NewRequest("GET", "http://localhost/foo", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -1282,8 +1281,8 @@ func TestHandler_Event_TypeScript(t *testing.T) { } op := openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", openapitest.NewContent()))) - openapitest.AppendPath("/foo", config, openapitest.WithOperation("get", op)) + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json"))) + openapitest.AppendPath("/foo", config, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -1321,25 +1320,25 @@ func TestHandler_Event_TypeScript(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", - openapitest.NewContent(openapitest.WithSchema( + openapitest.WithSchema( schematest.New( "object", schematest.WithProperty("foo", schematest.New("string")), schematest.WithRequired("foo"), ), - )), + ), )), openapitest.WithResponse(http.StatusNotFound, openapitest.WithContent("application/json", - openapitest.NewContent(openapitest.WithSchema( + openapitest.WithSchema( schematest.New( "object", schematest.WithProperty("message", schematest.New("string")), schematest.WithRequired("message"), ), - )), + ), )), ) - openapitest.AppendPath("/foo", config, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", config, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -1377,16 +1376,16 @@ func TestHandler_Event_TypeScript(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", - openapitest.NewContent(openapitest.WithSchema( + openapitest.WithSchema( schematest.New( "object", schematest.WithProperty("foo", schematest.New("string")), schematest.WithRequired("foo"), ), - )), + ), )), ) - openapitest.AppendPath("/foo", config, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", config, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -1429,26 +1428,26 @@ func TestHandler_Event_TypeScript(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", - openapitest.NewContent(openapitest.WithSchema( + openapitest.WithSchema( schematest.New( "object", schematest.WithProperty("foo", schematest.New("string")), schematest.WithRequired("foo"), ), - )), + ), ), openapitest.WithContent("application/xml", - openapitest.NewContent(openapitest.WithSchema( + openapitest.WithSchema( schematest.New( "object", schematest.WithProperty("foo", schematest.New("string")), schematest.WithRequired("foo"), ), - )), + ), ), ), ) - openapitest.AppendPath("/foo", config, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", config, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -1486,25 +1485,25 @@ func TestHandler_Event_TypeScript(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", - openapitest.NewContent(openapitest.WithSchema( + openapitest.WithSchema( schematest.New( "object", schematest.WithProperty("foo", schematest.New("string")), schematest.WithRequired("foo"), ), - )), + ), )), openapitest.WithResponse(http.StatusNotFound, openapitest.WithContent("application/json", - openapitest.NewContent(openapitest.WithSchema( + openapitest.WithSchema( schematest.New( "object", schematest.WithProperty("message", schematest.New("string")), schematest.WithRequired("message"), ), - )), + ), )), ) - openapitest.AppendPath("/foo", config, openapitest.WithOperation("get", op)) + openapitest.AppendPath("/foo", config, openapitest.UseOperation("get", op)) r := httptest.NewRequest("get", "http://localhost/foo", nil) r.Header.Set("accept", "application/json") rr := httptest.NewRecorder() @@ -1540,10 +1539,10 @@ func TestHandler_Parameter(t *testing.T) { op := openapitest.NewOperation( openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", openapitest.NewContent()), + openapitest.WithContent("application/json"), )) openapitest.AppendPath("/foo/{id}", c, - openapitest.WithOperation(http.MethodPost, op), + openapitest.UseOperation(http.MethodPost, op), ) r := httptest.NewRequest("post", "http://localhost/foo/123", strings.NewReader(`{ "foo": "bar" }`)) r.Header.Set("Content-Type", "application/json") diff --git a/providers/openapi/header_test.go b/providers/openapi/header_test.go index 9f813229a..e6b15162c 100644 --- a/providers/openapi/header_test.go +++ b/providers/openapi/header_test.go @@ -3,8 +3,6 @@ package openapi_test import ( "encoding/json" "fmt" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/providers/openapi" @@ -13,6 +11,9 @@ import ( "net/http" "net/url" "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestHeader_UnmarshalJSON(t *testing.T) { @@ -241,12 +242,12 @@ func TestHeader_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, - openapitest.WithResponseHeaderRef("foo", nil), - ))), - )), + openapitest.UseResponseHeaderRef("foo", nil), + )), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -259,12 +260,12 @@ func TestHeader_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, openapitest.WithResponseHeader("foo", "foo description", schematest.New("string")), - ))), - )), + )), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -279,12 +280,12 @@ func TestHeader_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, - openapitest.WithResponseHeaderRef("foo", &openapi.HeaderRef{Reference: dynamic.Reference{Ref: "foo"}}), - ))), - )), + openapitest.WithResponseHeaderRef("foo", "foo"), + )), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse response '200' failed: parse header 'foo' failed: resolve reference 'foo' failed: TEST ERROR") @@ -312,25 +313,25 @@ func TestConfig_Patch_Header(t *testing.T) { name: "add header", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{}, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{}, + }), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, + }, + }), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -341,27 +342,27 @@ func TestConfig_Patch_Header(t *testing.T) { name: "patch header", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, + }, + }), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "bar"}}}, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "bar"}}}, + }, + }), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -372,27 +373,27 @@ func TestConfig_Patch_Header(t *testing.T) { name: "source header is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": {Value: nil}, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": {Value: nil}, + }, + }), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, + }, + }), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -403,27 +404,27 @@ func TestConfig_Patch_Header(t *testing.T) { name: "source header value is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": {Value: nil}, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": {Value: nil}, + }, + }), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, + }, + }), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -434,27 +435,27 @@ func TestConfig_Patch_Header(t *testing.T) { name: "patch headers is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, + }, + }), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": nil, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": nil, + }, + }), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -465,27 +466,27 @@ func TestConfig_Patch_Header(t *testing.T) { name: "patch header value is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": {Value: &openapi.Header{Parameter: openapi.Parameter{Description: "foo"}}}, + }, + }), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(200, &openapi.ResponseRef{Value: &openapi.Response{ - Headers: map[string]*openapi.HeaderRef{ - "foo": {}, - }, - }}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(200, &openapi.Response{ + Headers: map[string]*openapi.HeaderRef{ + "foo": {}, + }, + }), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) diff --git a/providers/openapi/media_type.go b/providers/openapi/media_type.go index 37a0a3171..f7c05bdc6 100644 --- a/providers/openapi/media_type.go +++ b/providers/openapi/media_type.go @@ -23,6 +23,11 @@ func (m *MediaType) parse(config *dynamic.Config, reader dynamic.Reader) error { if m == nil { return nil } + + scope := m.ContentType.String() + config.OpenScope(scope) + defer config.CloseScope() + if err := m.Schema.Parse(config, reader); err != nil { return fmt.Errorf("parse schema failed: %s", err) } diff --git a/providers/openapi/media_type_test.go b/providers/openapi/media_type_test.go index 3e1102159..c86974cec 100644 --- a/providers/openapi/media_type_test.go +++ b/providers/openapi/media_type_test.go @@ -3,8 +3,6 @@ package openapi_test import ( "encoding/json" "fmt" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/providers/openapi" @@ -13,6 +11,9 @@ import ( "net/http" "net/url" "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestMediaType_UnmarshalJSON(t *testing.T) { @@ -109,12 +110,12 @@ func TestMediaType_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", &openapi.MediaType{}), - ))), - )), + openapitest.WithContent("application/json"), + )), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -127,12 +128,12 @@ func TestMediaType_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", nil), - ))), - )), + openapitest.UseContent("application/json", nil), + )), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -145,18 +146,16 @@ func TestMediaType_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", - openapitest.NewContent( - openapitest.WithSchemaRef("foo.yml"), - ), + + openapitest.WithSchemaRef("foo.yml"), ), ), ), - ), - )), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse response '200' failed: parse content 'application/json' failed: parse schema failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -169,16 +168,15 @@ func TestMediaType_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK, - openapitest.WithContent("application/json", + openapitest.UseContent("application/json", &openapi.MediaType{Examples: map[string]*openapi.ExampleRef{"foo": {Reference: dynamic.Reference{Ref: "foo.yml"}}}}, ), ), ), - ), - )), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse response '200' failed: parse content 'application/json' failed: parse example 'foo' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -206,19 +204,19 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "patch is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", &openapi.MediaType{})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/plain")), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", nil)), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", nil)), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -231,19 +229,19 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "add schema", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/html")), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{Schema: schematest.New("string")})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{Schema: schematest.New("string")})), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -255,19 +253,19 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "patch schema", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{Schema: schematest.New("string")})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{Schema: schematest.New("string")})), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{Schema: schematest.New("object")})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{Schema: schematest.New("object")})), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -279,19 +277,19 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "add example", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/html")), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{Example: &openapi.ExampleValue{Value: "foo"}})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{Example: &openapi.ExampleValue{Value: "foo"}})), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -303,19 +301,19 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "patch example", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{Example: &openapi.ExampleValue{Value: "foo"}})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{Example: &openapi.ExampleValue{Value: "foo"}})), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{Example: &openapi.ExampleValue{Value: "bar"}})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{Example: &openapi.ExampleValue{Value: "bar"}})), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -327,19 +325,19 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "patch example is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{Example: &openapi.ExampleValue{Value: "foo"}})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{Example: &openapi.ExampleValue{Value: "foo"}})), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{Example: nil})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{Example: nil})), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -351,21 +349,21 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "add examples", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/html")), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "foo"}}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "foo"}}}, + })), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -377,23 +375,23 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "patch examples foo", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "foo"}}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "foo"}}}, + })), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "bar"}}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "bar"}}}, + })), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -405,21 +403,21 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "patch example is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "foo"}}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "foo"}}}, + })), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{Examples: nil})), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{Examples: nil})), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -431,23 +429,23 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "patch examples overwrites example", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{ - Example: &openapi.ExampleValue{Value: "foo"}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{ + Example: &openapi.ExampleValue{Value: "foo"}, + })), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "bar"}}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "bar"}}}, + })), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -460,23 +458,23 @@ func TestConfig_Patch_MediaType(t *testing.T) { name: "patch example overwrites examples", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{ - Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "foo"}}}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{ + Examples: map[string]*openapi.ExampleRef{"foo": {Value: &openapi.Example{Value: "foo"}}}, + })), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/html", &openapi.MediaType{ - Example: &openapi.ExampleValue{Value: "bar"}, - })), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.UseContent("text/html", &openapi.MediaType{ + Example: &openapi.ExampleValue{Value: "bar"}, + })), ), - ))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) diff --git a/providers/openapi/openapitest/config.go b/providers/openapi/openapitest/config.go index b1db30fb0..7e6bd5493 100644 --- a/providers/openapi/openapitest/config.go +++ b/providers/openapi/openapitest/config.go @@ -56,8 +56,9 @@ func WithTag(name, summary, description string) ConfigOptions { } } -func WithPath(name string, path *openapi.Path) ConfigOptions { +func WithPath(name string, opts ...PathOptions) ConfigOptions { return func(c *openapi.Config) { + path := NewPath(opts...) if path != nil { path.Path = name } @@ -65,6 +66,15 @@ func WithPath(name string, path *openapi.Path) ConfigOptions { } } +func UsePath(name string, p *openapi.Path) ConfigOptions { + return func(c *openapi.Config) { + if p != nil { + p.Path = name + } + c.Paths[name] = &openapi.PathRef{Value: p} + } +} + func WithPathRef(name string, ref *openapi.PathRef) ConfigOptions { return func(c *openapi.Config) { if ref != nil && ref.Value != nil { diff --git a/providers/openapi/openapitest/operation.go b/providers/openapi/openapitest/operation.go index 5a370395b..8defe7640 100644 --- a/providers/openapi/openapitest/operation.go +++ b/providers/openapi/openapitest/operation.go @@ -1,6 +1,7 @@ package openapitest import ( + "mokapi/config/dynamic" "mokapi/media" "mokapi/providers/openapi" "mokapi/providers/openapi/schema" @@ -36,7 +37,21 @@ func WithResponse(status int, opts ...ResponseOptions) OperationOptions { } } -func WithResponseRef(status int, ref *openapi.ResponseRef) OperationOptions { +func UseResponse(status int, r *openapi.Response) OperationOptions { + return func(o *openapi.Operation) { + o.Responses.Set(strconv.Itoa(status), &openapi.ResponseRef{Value: r}) + } +} + +func WithResponseRef(status int, ref string) OperationOptions { + return func(o *openapi.Operation) { + o.Responses.Set(strconv.Itoa(status), &openapi.ResponseRef{ + Reference: dynamic.Reference{Ref: ref}, + }) + } +} + +func UseResponseRef(status int, ref *openapi.ResponseRef) OperationOptions { return func(o *openapi.Operation) { o.Responses.Set(strconv.Itoa(status), ref) } @@ -52,10 +67,10 @@ func WithOperationParam(name string, required bool, opts ...ParamOptions) Operat } } -func WithOperationParamRef(ref *openapi.ParameterRef) OperationOptions { +func WithOperationParamRef(ref string) OperationOptions { return func(o *openapi.Operation) { - o.Parameters = append(o.Parameters, ref) + o.Parameters = append(o.Parameters, &openapi.ParameterRef{Reference: dynamic.Reference{Ref: ref}}) } } @@ -110,6 +125,14 @@ func WithRequestBody(description string, required bool, opts ...RequestBodyOptio } } +func WithRequestBodyRef(ref string) OperationOptions { + return func(o *openapi.Operation) { + o.RequestBody = &openapi.RequestBodyRef{ + Reference: dynamic.Reference{Ref: ref}, + } + } +} + func newParam(name string, required bool, t openapi.Location, opts ...ParamOptions) *openapi.Parameter { p := &openapi.Parameter{ Name: name, diff --git a/providers/openapi/openapitest/path.go b/providers/openapi/openapitest/path.go index 88b52aa1f..7eade336b 100644 --- a/providers/openapi/openapitest/path.go +++ b/providers/openapi/openapitest/path.go @@ -1,6 +1,7 @@ package openapitest import ( + "mokapi/config/dynamic" "mokapi/providers/openapi" "strings" ) @@ -36,7 +37,7 @@ func AppendPath(path string, config *openapi.Config, opts ...PathOptions) *opena return e } -func WithOperation(method string, op *openapi.Operation) PathOptions { +func UseOperation(method string, op *openapi.Operation) PathOptions { return func(e *openapi.Path) { switch strings.ToUpper(method) { case "GET": @@ -67,6 +68,11 @@ func WithOperation(method string, op *openapi.Operation) PathOptions { } } +func WithOperation(method string, opts ...OperationOptions) PathOptions { + op := NewOperation(opts...) + return UseOperation(method, op) +} + func WithPathParam(name string, opts ...ParamOptions) PathOptions { return func(e *openapi.Path) { e.Parameters = append(e.Parameters, &openapi.ParameterRef{ @@ -74,8 +80,10 @@ func WithPathParam(name string, opts ...ParamOptions) PathOptions { } } -func WithPathParamRef(ref *openapi.ParameterRef) PathOptions { +func WithPathParamRef(ref string) PathOptions { return func(e *openapi.Path) { - e.Parameters = append(e.Parameters, ref) + e.Parameters = append(e.Parameters, &openapi.ParameterRef{ + Reference: dynamic.Reference{Ref: ref}, + }) } } diff --git a/providers/openapi/openapitest/response.go b/providers/openapi/openapitest/response.go index 2663fedf4..8781a5be8 100644 --- a/providers/openapi/openapitest/response.go +++ b/providers/openapi/openapitest/response.go @@ -1,6 +1,7 @@ package openapitest import ( + "mokapi/config/dynamic" "mokapi/media" "mokapi/providers/openapi" "mokapi/providers/openapi/schema" @@ -35,7 +36,16 @@ func WithResponseHeader(name, description string, s *schema.Schema) ResponseOpti } } -func WithResponseHeaderRef(name string, ref *openapi.HeaderRef) ResponseOptions { +func WithResponseHeaderRef(name string, ref string) ResponseOptions { + return func(o *openapi.Response) { + if o.Headers == nil { + o.Headers = map[string]*openapi.HeaderRef{} + } + o.Headers[name] = &openapi.HeaderRef{Reference: dynamic.Reference{Ref: ref}} + } +} + +func UseResponseHeaderRef(name string, ref *openapi.HeaderRef) ResponseOptions { return func(o *openapi.Response) { if o.Headers == nil { o.Headers = map[string]*openapi.HeaderRef{} @@ -50,9 +60,10 @@ func WithResponseDescription(description string) ResponseOptions { } } -func WithContent(mediaType string, content *openapi.MediaType) ResponseOptions { +func WithContent(mediaType string, opts ...ContentOptions) ResponseOptions { return func(o *openapi.Response) { ct := media.ParseContentType(mediaType) + content := NewContent(opts...) o.Content[mediaType] = content if content != nil { content.ContentType = ct @@ -60,6 +71,18 @@ func WithContent(mediaType string, content *openapi.MediaType) ResponseOptions { } } +func UseContent(mediaType string, mt *openapi.MediaType) ResponseOptions { + return func(o *openapi.Response) { + if o.Content == nil { + o.Content = map[string]*openapi.MediaType{} + } + o.Content[mediaType] = mt + if mt != nil { + mt.ContentType = media.ParseContentType(mediaType) + } + } +} + func NewContent(opts ...ContentOptions) *openapi.MediaType { mt := &openapi.MediaType{} for _, opt := range opts { @@ -78,12 +101,21 @@ func WithEncoding(propName string, encoding *openapi.Encoding) ContentOptions { } } -func WithExample(example interface{}) ContentOptions { +func WithExampleValue(example interface{}) ContentOptions { return func(c *openapi.MediaType) { c.Example = &openapi.ExampleValue{Value: example} } } +func WithExampleRef(name, ref string) ContentOptions { + return func(c *openapi.MediaType) { + if c.Examples == nil { + c.Examples = map[string]*openapi.ExampleRef{} + } + c.Examples[name] = &openapi.ExampleRef{Reference: dynamic.Reference{Ref: ref}} + } +} + func WithSchema(s *schema.Schema) ContentOptions { return func(c *openapi.MediaType) { c.Schema = s diff --git a/providers/openapi/operation_test.go b/providers/openapi/operation_test.go index efd734146..e4eb51271 100644 --- a/providers/openapi/operation_test.go +++ b/providers/openapi/operation_test.go @@ -3,8 +3,6 @@ package openapi_test import ( "encoding/json" "fmt" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/providers/openapi" @@ -13,6 +11,9 @@ import ( "net/url" "strings" "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestOperation_UnmarshalJSON(t *testing.T) { @@ -209,9 +210,9 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -226,11 +227,9 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation(http.MethodGet, - openapitest.NewOperation( - openapitest.WithOperationParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}))), - )), + openapitest.WithOperationParamRef("foo.yml"))), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse parameter index '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -243,9 +242,9 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodPost, &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodPost), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -260,11 +259,9 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation(http.MethodPost, - openapitest.NewOperation( - openapitest.WithOperationParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}))), - )), + openapitest.WithOperationParamRef("foo.yml"))), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'POST' failed: parse parameter index '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -277,9 +274,9 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodPut, &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodPut), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -294,11 +291,9 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation(http.MethodPut, - openapitest.NewOperation( - openapitest.WithOperationParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}))), - )), + openapitest.WithOperationParamRef("foo.yml"))), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'PUT' failed: parse parameter index '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -311,9 +306,9 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodPatch, &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodPatch), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -328,11 +323,9 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation(http.MethodPatch, - openapitest.NewOperation( - openapitest.WithOperationParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}))), - )), + openapitest.WithOperationParamRef("foo.yml"))), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'PATCH' failed: parse parameter index '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -345,9 +338,9 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodDelete, &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodDelete), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -362,11 +355,9 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation(http.MethodDelete, - openapitest.NewOperation( - openapitest.WithOperationParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}))), - )), + openapitest.WithOperationParamRef("foo.yml"))), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'DELETE' failed: parse parameter index '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -379,9 +370,9 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodHead, &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodHead), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -396,11 +387,9 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation(http.MethodHead, - openapitest.NewOperation( - openapitest.WithOperationParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}))), - )), + openapitest.WithOperationParamRef("foo.yml"))), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'HEAD' failed: parse parameter index '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -413,9 +402,9 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodOptions, &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodOptions), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -430,11 +419,9 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation(http.MethodOptions, - openapitest.NewOperation( - openapitest.WithOperationParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}))), - )), + openapitest.WithOperationParamRef("foo.yml"))), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'OPTIONS' failed: parse parameter index '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -447,9 +434,9 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodTrace, &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodTrace), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -464,11 +451,9 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation(http.MethodTrace, - openapitest.NewOperation( - openapitest.WithOperationParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}))), - )), + openapitest.WithOperationParamRef("foo.yml"))), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'TRACE' failed: parse parameter index '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -481,9 +466,9 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation("QUERY", &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation("QUERY"), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -498,11 +483,9 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation("QUERY", - openapitest.NewOperation( - openapitest.WithOperationParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}))), - )), + openapitest.WithOperationParamRef("foo.yml"))), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'QUERY' failed: parse parameter index '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -515,9 +498,9 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation("LINK", &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation("LINK"), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -532,11 +515,9 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( + openapitest.WithPath("/foo", openapitest.WithOperation("LINK", - openapitest.NewOperation( - openapitest.WithOperationParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}))), - )), + openapitest.WithOperationParamRef("foo.yml"))), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'LINK' failed: parse parameter index '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -549,11 +530,11 @@ func TestOperation_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodTrace, &openapi.Operation{ + openapitest.WithPath("/foo", + openapitest.UseOperation(http.MethodTrace, &openapi.Operation{ RequestBody: &openapi.RequestBodyRef{Value: &openapi.RequestBody{Description: "foo"}}, }), - )), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -573,11 +554,11 @@ func TestOperation_Parse(t *testing.T) { return cfg, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodTrace, &openapi.Operation{ + openapitest.WithPath("/foo", + openapitest.UseOperation(http.MethodTrace, &openapi.Operation{ RequestBody: &openapi.RequestBodyRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/requestBodies/foo"}}, }), - )), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -592,11 +573,11 @@ func TestOperation_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodTrace, &openapi.Operation{ + openapitest.WithPath("/foo", + openapitest.UseOperation(http.MethodTrace, &openapi.Operation{ RequestBody: &openapi.RequestBodyRef{Reference: dynamic.Reference{Ref: "foo.yml#/components/requestBodies/foo"}}, }), - )), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'TRACE' failed: parse request body failed: resolve reference 'foo.yml#/components/requestBodies/foo' failed: TEST ERROR") @@ -628,9 +609,9 @@ func TestConfig_Patch_Operation(t *testing.T) { name: "patch path with " + m, configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath())), + "/foo")), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation(m, openapitest.NewOperation())))), + "/foo", openapitest.WithOperation(m))), }, test: func(t *testing.T, result *openapi.Config) { require.Len(t, result.Paths, 1) @@ -642,11 +623,11 @@ func TestConfig_Patch_Operation(t *testing.T) { name: fmt.Sprintf("patch path %v info", m), configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation(m, openapitest.NewOperation())))), + "/foo", openapitest.WithOperation(m))), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation(m, openapitest.NewOperation( + "/foo", openapitest.WithOperation(m, openapitest.WithOperationInfo("foo", "bar", "id", true), - ))))), + ))), }, test: func(t *testing.T, result *openapi.Config) { require.Len(t, result.Paths, 1) @@ -664,9 +645,9 @@ func TestConfig_Patch_Operation(t *testing.T) { name: "patch is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation("get", openapitest.NewOperation())))), + "/foo", openapitest.WithOperation("get"))), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath())), + "/foo")), }, test: func(t *testing.T, result *openapi.Config) { require.Len(t, result.Paths, 1) diff --git a/providers/openapi/parse_test.go b/providers/openapi/parse_test.go index 420d8b2d4..2c9ae2a10 100644 --- a/providers/openapi/parse_test.go +++ b/providers/openapi/parse_test.go @@ -1,7 +1,6 @@ package openapi_test import ( - "github.com/stretchr/testify/require" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/providers/openapi" @@ -9,6 +8,8 @@ import ( "mokapi/providers/openapi/schema" "mokapi/providers/openapi/schema/schematest" "testing" + + "github.com/stretchr/testify/require" ) func Test_Parse(t *testing.T) { @@ -17,34 +18,18 @@ func Test_Parse(t *testing.T) { c *openapi.Config test func(t *testing.T, c *openapi.Config, err error) }{ - { - name: "schemas reference", - c: openapitest.NewConfig("3.1", - openapitest.WithComponentSchema("Foo", schematest.New("array", schematest.WithItemsRef("#/components/schemas/Bar"))), - openapitest.WithComponentSchema("Bar", schematest.New("string")), - ), - test: func(t *testing.T, c *openapi.Config, err error) { - require.NoError(t, err) - foo := c.Components.Schemas.Get("Foo") - require.Equal(t, "array", foo.Type.String()) - bar := c.Components.Schemas.Get("Bar") - require.Equal(t, "string", bar.Type.String()) - // reference should point to same schema - require.Equal(t, bar, foo.Items.Sub) - }, - }, { name: "response schema reference should point to same objects", c: openapitest.NewConfig("3.1", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation("get", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("application/json", + openapitest.WithPath("/foo", + openapitest.WithOperation("get", + openapitest.WithResponse(200, openapitest.UseContent("application/json", &openapi.MediaType{ Schema: &schema.Schema{Ref: "#/components/schemas/Foo"}, }, - )), + ), + ), )), - )), openapitest.WithComponentSchema("Foo", schematest.New("array", schematest.WithItemsRef("#/components/schemas/Bar"))), openapitest.WithComponentSchema("Bar", schematest.New("string")), ), @@ -81,35 +66,19 @@ func Test_ParseAndPatch(t *testing.T) { configs []*openapi.Config test func(t *testing.T, c *openapi.Config) }{ - { - name: "schemas reference", - configs: []*openapi.Config{ - openapitest.NewConfig("3.1", - openapitest.WithComponentSchema("Foo", schematest.New("array", schematest.WithItemsRef("#/components/schemas/Bar"))), - openapitest.WithComponentSchema("Bar", schematest.New("string")), - ), - openapitest.NewConfig("3.1", - openapitest.WithComponentSchema("Bar", schematest.New("string", schematest.IsNullable(true))), - ), - }, - test: func(t *testing.T, c *openapi.Config) { - foo := c.Components.Schemas.Get("Foo") - require.True(t, foo.Items.Nullable) - }, - }, { name: "response schema reference should point to same objects", configs: []*openapi.Config{ openapitest.NewConfig("3.1", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation("get", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("application/json", + openapitest.WithPath("/foo", + openapitest.WithOperation("get", + openapitest.WithResponse(200, openapitest.UseContent("application/json", &openapi.MediaType{ Schema: &schema.Schema{Ref: "#/components/schemas/Foo"}, }, - )), + ), + ), )), - )), openapitest.WithComponentSchema("Foo", schematest.New("array", schematest.WithItemsRef("#/components/schemas/Bar"))), openapitest.WithComponentSchema("Bar", schematest.New("string")), ), @@ -132,11 +101,6 @@ func Test_ParseAndPatch(t *testing.T) { t.Parallel() var target *openapi.Config for _, c := range tc.configs { - err := c.Parse(&dynamic.Config{ - Info: dynamictest.NewConfigInfo(), - Data: c, - }, &dynamictest.Reader{}) - require.NoError(t, err) if target == nil { target = c } else { @@ -144,6 +108,12 @@ func Test_ParseAndPatch(t *testing.T) { } } + err := target.Parse(&dynamic.Config{ + Info: dynamictest.NewConfigInfo(), + Data: target, + }, &dynamictest.Reader{}) + require.NoError(t, err) + tc.test(t, target) }) } diff --git a/providers/openapi/patch_test.go b/providers/openapi/patch_test.go index d9cf71804..91384b21b 100644 --- a/providers/openapi/patch_test.go +++ b/providers/openapi/patch_test.go @@ -1,11 +1,12 @@ package openapi_test import ( - "github.com/stretchr/testify/require" "mokapi/providers/openapi" "mokapi/providers/openapi/openapitest" "mokapi/providers/openapi/schema/schematest" "testing" + + "github.com/stretchr/testify/require" ) func TestConfig_Patch_Methods_RequestBody(t *testing.T) { @@ -18,15 +19,15 @@ func TestConfig_Patch_Methods_RequestBody(t *testing.T) { name: "description and required", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation(), + "/foo", openapitest.WithOperation( + "post", ), - ))), + )), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation(openapitest.WithRequestBody("foo", true)), - ), - ))), + "/foo", openapitest.WithOperation( + "post", openapitest.WithRequestBody("foo", true)), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { require.Equal(t, "foo", result.Paths["/foo"].Value.Post.RequestBody.Value.Description) @@ -37,15 +38,15 @@ func TestConfig_Patch_Methods_RequestBody(t *testing.T) { name: "patch description and required", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation(openapitest.WithRequestBody("", false)), - ), - ))), + "/foo", openapitest.WithOperation( + "post", openapitest.WithRequestBody("", false)), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation(openapitest.WithRequestBody("foo", true)), - ), - ))), + "/foo", openapitest.WithOperation( + "post", openapitest.WithRequestBody("foo", true)), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { require.Equal(t, "foo", result.Paths["/foo"].Value.Post.RequestBody.Value.Description) @@ -56,19 +57,19 @@ func TestConfig_Patch_Methods_RequestBody(t *testing.T) { name: "patch add content type", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithRequestBody("foo", true, - openapitest.WithRequestContent("text/plain", &openapi.MediaType{}))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithRequestBody("foo", true, + openapitest.WithRequestContent("text/plain", &openapi.MediaType{}))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithRequestBody("foo", true, - openapitest.WithRequestContent("application/json", &openapi.MediaType{}))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithRequestBody("foo", true, + openapitest.WithRequestContent("application/json", &openapi.MediaType{}))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { body := result.Paths["/foo"].Value.Post.RequestBody.Value @@ -80,20 +81,20 @@ func TestConfig_Patch_Methods_RequestBody(t *testing.T) { name: "add content type schema", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithRequestBody("foo", true, - openapitest.WithRequestContent("text/plain", &openapi.MediaType{}))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithRequestBody("foo", true, + openapitest.WithRequestContent("text/plain", &openapi.MediaType{}))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithRequestBody("foo", true, - openapitest.WithRequestContent("text/plain", - openapitest.NewContent(openapitest.WithSchema(schematest.New("number")))))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithRequestBody("foo", true, + openapitest.WithRequestContent("text/plain", + openapitest.NewContent(openapitest.WithSchema(schematest.New("number")))))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { body := result.Paths["/foo"].Value.Post.RequestBody.Value @@ -105,21 +106,21 @@ func TestConfig_Patch_Methods_RequestBody(t *testing.T) { name: "patch content type schema", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithRequestBody("foo", true, - openapitest.WithRequestContent("text/plain", - openapitest.NewContent(openapitest.WithSchema(schematest.New("number")))))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithRequestBody("foo", true, + openapitest.WithRequestContent("text/plain", + openapitest.NewContent(openapitest.WithSchema(schematest.New("number")))))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithRequestBody("foo", true, - openapitest.WithRequestContent("text/plain", - openapitest.NewContent(openapitest.WithSchema(schematest.New("number", schematest.WithFormat("double"))))))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithRequestBody("foo", true, + openapitest.WithRequestContent("text/plain", + openapitest.NewContent(openapitest.WithSchema(schematest.New("number", schematest.WithFormat("double"))))))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { body := result.Paths["/foo"].Value.Post.RequestBody.Value @@ -132,20 +133,20 @@ func TestConfig_Patch_Methods_RequestBody(t *testing.T) { name: "patch content type example", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithRequestBody("foo", true, - openapitest.WithRequestContent("text/plain", &openapi.MediaType{}))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithRequestBody("foo", true, + openapitest.WithRequestContent("text/plain", &openapi.MediaType{}))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithRequestBody("foo", true, - openapitest.WithRequestContent("text/plain", - openapitest.NewContent(openapitest.WithExample(12))))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithRequestBody("foo", true, + openapitest.WithRequestContent("text/plain", + openapitest.NewContent(openapitest.WithExampleValue(12))))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { body := result.Paths["/foo"].Value.Post.RequestBody.Value @@ -186,16 +187,16 @@ func TestConfig_Patch_Methods_RequestBody(t *testing.T) { name: "patch operation security", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation(), + "/foo", openapitest.WithOperation( + "post", ), - ))), + )), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithSecurity(map[string][]string{"foo": {}})), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithSecurity(map[string][]string{"foo": {}})), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { require.Len(t, result.Paths["/foo"].Value.Post.Security, 1) @@ -206,18 +207,18 @@ func TestConfig_Patch_Methods_RequestBody(t *testing.T) { name: "patch operation security add scope", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithSecurity(map[string][]string{"foo": {"foo"}}), - ), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithSecurity(map[string][]string{"foo": {"foo"}}), ), - ))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithSecurity(map[string][]string{"foo": {"foo", "bar"}})), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithSecurity(map[string][]string{"foo": {"foo", "bar"}})), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { require.Len(t, result.Paths["/foo"].Value.Post.Security, 2) diff --git a/providers/openapi/path.go b/providers/openapi/path.go index 56feae9a6..9114ce9a6 100644 --- a/providers/openapi/path.go +++ b/providers/openapi/path.go @@ -177,9 +177,9 @@ func (p *Path) parse(config *dynamic.Config, reader dynamic.Reader) error { return nil } - for _, param := range p.Parameters { + for index, param := range p.Parameters { if err := param.Parse(config, reader); err != nil { - return err + return fmt.Errorf("parse parameter '%v' failed: %w", index, err) } } diff --git a/providers/openapi/path_test.go b/providers/openapi/path_test.go index 4d437463b..f8b4454da 100644 --- a/providers/openapi/path_test.go +++ b/providers/openapi/path_test.go @@ -454,7 +454,7 @@ func TestPath_Parse(t *testing.T) { require.Equal(t, "/foo.yml", u.String()) cfg := &dynamic.Config{Info: dynamic.ConfigInfo{Url: u}, Data: openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", target)), + openapitest.UsePath("/foo", target)), } return cfg, nil }) @@ -473,7 +473,7 @@ func TestPath_Parse(t *testing.T) { require.Equal(t, "/foo.yml", u.String()) cfg := &dynamic.Config{Info: dynamic.ConfigInfo{Url: u}, Data: openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", nil)), + openapitest.UsePath("/foo", nil)), } return cfg, nil }) @@ -511,12 +511,12 @@ func TestPath_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithPathParamRef(&openapi.ParameterRef{Reference: dynamic.Reference{Ref: "foo.yml"}}), - )), + openapitest.WithPath("/foo", + openapitest.WithPathParamRef("foo.yml"), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) - require.EqualError(t, err, "parse path '/foo' failed: resolve reference 'foo.yml' failed: TEST ERROR") + require.EqualError(t, err, "parse path '/foo' failed: parse parameter '0' failed: resolve reference 'foo.yml' failed: TEST ERROR") }, }, { @@ -557,10 +557,10 @@ func TestConfig_Patch_Path(t *testing.T) { { name: "patch both path are nil", configs: []*openapi.Config{ - openapitest.NewConfig("1.0", openapitest.WithPath( + openapitest.NewConfig("1.0", openapitest.UsePath( "/foo", nil, )), - openapitest.NewConfig("1.0", openapitest.WithPath( + openapitest.NewConfig("1.0", openapitest.UsePath( "/foo", nil, )), }, @@ -575,12 +575,12 @@ func TestConfig_Patch_Path(t *testing.T) { configs: []*openapi.Config{ {Paths: map[string]*openapi.PathRef{"/foo": nil}}, openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath( - openapitest.WithOperation( - "post", openapitest.NewOperation(), - ), + "/foo", + openapitest.WithOperation( + "post", ), - )), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { require.Len(t, result.Paths, 1) @@ -592,10 +592,10 @@ func TestConfig_Patch_Path(t *testing.T) { name: "patch without path", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation(), + "/foo", openapitest.WithOperation( + "post", ), - ))), + )), openapitest.NewConfig("1.0"), }, test: func(t *testing.T, result *openapi.Config) { @@ -609,10 +609,10 @@ func TestConfig_Patch_Path(t *testing.T) { configs: []*openapi.Config{ openapitest.NewConfig("1.0"), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation(), + "/foo", openapitest.WithOperation( + "post", ), - ))), + )), }, test: func(t *testing.T, result *openapi.Config) { require.Len(t, result.Paths, 1) @@ -626,10 +626,10 @@ func TestConfig_Patch_Path(t *testing.T) { openapitest.NewConfig("1.0", openapitest.WithPathRef( "/foo", &openapi.PathRef{})), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation(), + "/foo", openapitest.WithOperation( + "post", ), - ))), + )), }, test: func(t *testing.T, result *openapi.Config) { require.Len(t, result.Paths, 1) @@ -641,9 +641,9 @@ func TestConfig_Patch_Path(t *testing.T) { name: "patch summary and description", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath())), + "/foo")), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithPathInfo("foo", "bar")))), + "/foo", openapitest.WithPathInfo("foo", "bar"))), }, test: func(t *testing.T, result *openapi.Config) { require.Len(t, result.Paths, 1) @@ -656,9 +656,9 @@ func TestConfig_Patch_Path(t *testing.T) { name: "add parameters", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath())), + "/foo")), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithPathParam("foo")))), + "/foo", openapitest.WithPathParam("foo"))), }, test: func(t *testing.T, result *openapi.Config) { e := result.Paths["/foo"].Value @@ -669,9 +669,9 @@ func TestConfig_Patch_Path(t *testing.T) { name: "patch parameters", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithPathParam("foo")))), + "/foo", openapitest.WithPathParam("foo"))), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithPathParam("foo", openapitest.WithParamSchema(schematest.New("number")))))), + "/foo", openapitest.WithPathParam("foo", openapitest.WithParamSchema(schematest.New("number"))))), }, test: func(t *testing.T, result *openapi.Config) { e := result.Paths["/foo"].Value diff --git a/providers/openapi/response.go b/providers/openapi/response.go index 5f4cb5ff8..08acce787 100644 --- a/providers/openapi/response.go +++ b/providers/openapi/response.go @@ -218,10 +218,10 @@ func (r *ResponseRef) parse(config *dynamic.Config, reader dynamic.Reader) error return dynamic.Resolve(r.Ref, &r.Value, config, reader) } - return r.Value.parse(config, reader) + return r.Value.Parse(config, reader) } -func (r *Response) parse(config *dynamic.Config, reader dynamic.Reader) error { +func (r *Response) Parse(config *dynamic.Config, reader dynamic.Reader) error { if r == nil { return nil } diff --git a/providers/openapi/response_test.go b/providers/openapi/response_test.go index a3d1cf1ef..62a3ca9a7 100644 --- a/providers/openapi/response_test.go +++ b/providers/openapi/response_test.go @@ -309,11 +309,11 @@ func TestResponse_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK), - )), - )), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -326,9 +326,9 @@ func TestResponse_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, &openapi.Operation{}), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -341,11 +341,11 @@ func TestResponse_Parse(t *testing.T) { return nil, nil }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( - openapitest.WithResponseRef(http.StatusOK, nil), - )), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.UseResponse(http.StatusOK, nil), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.NoError(t, err) @@ -358,11 +358,11 @@ func TestResponse_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( - openapitest.WithResponseRef(http.StatusOK, &openapi.ResponseRef{Reference: dynamic.Reference{Ref: "foo.yml"}}), - )), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponseRef(http.StatusOK, "foo.yml"), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse response '200' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -375,11 +375,11 @@ func TestResponse_Parse(t *testing.T) { return nil, fmt.Errorf("TEST ERROR") }) config := openapitest.NewConfig("3.0", - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( - openapitest.WithResponse(http.StatusOK, openapitest.WithResponseHeaderRef("foo", &openapi.HeaderRef{Reference: dynamic.Reference{Ref: "foo.yml"}})), - )), - )), + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, + openapitest.WithResponse(http.StatusOK, openapitest.WithResponseHeaderRef("foo", "foo.yml")), + ), + ), ) err := config.Parse(&dynamic.Config{Info: dynamic.ConfigInfo{Url: &url.URL{}}, Data: config}, reader) require.EqualError(t, err, "parse path '/foo' failed: parse operation 'GET' failed: parse response '200' failed: parse header 'foo' failed: resolve reference 'foo.yml' failed: TEST ERROR") @@ -415,20 +415,20 @@ func TestConfig_Patch_Response(t *testing.T) { name: "patch is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithResponseDescription("foo"))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithResponseDescription("foo"))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( + "/foo", openapitest.UseOperation( "post", &openapi.Operation{ Responses: &openapi.Responses{LinkedHashMap: getResponses(map[string]*openapi.ResponseRef{ "200": nil, })}, }), - ), - )), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -439,16 +439,16 @@ func TestConfig_Patch_Response(t *testing.T) { name: "add response", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", &openapi.Operation{}, + "/foo", openapitest.WithOperation( + "post", ), - ))), + )), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithResponseDescription("foo"))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithResponseDescription("foo"))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -459,17 +459,17 @@ func TestConfig_Patch_Response(t *testing.T) { name: "patch response", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(204, openapitest.WithResponseDescription("bar"))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(204, openapitest.WithResponseDescription("bar"))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithResponseDescription("foo"))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithResponseDescription("foo"))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(204) @@ -482,15 +482,15 @@ func TestConfig_Patch_Response(t *testing.T) { name: "patch is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(204, openapitest.WithResponseDescription("bar"))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(204, openapitest.WithResponseDescription("bar"))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", &openapi.Operation{}), - ))), + "/foo", openapitest.WithOperation( + "post"), + )), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(204) @@ -501,17 +501,17 @@ func TestConfig_Patch_Response(t *testing.T) { name: "patch response is nil", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(204, openapitest.WithResponseDescription("bar"))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(204, openapitest.WithResponseDescription("bar"))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponseRef(202, &openapi.ResponseRef{}), - ), - )))), + "/foo", openapitest.WithOperation( + "post", + openapitest.UseResponse(202, nil), + ), + )), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(202) @@ -522,17 +522,17 @@ func TestConfig_Patch_Response(t *testing.T) { name: "patch description", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200)), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200)), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithResponseDescription("foo"))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithResponseDescription("foo"))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -543,17 +543,17 @@ func TestConfig_Patch_Response(t *testing.T) { name: "patch add content type", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", openapitest.NewContent()))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/plain"))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("application/json", openapitest.NewContent()))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("application/json"))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -565,19 +565,18 @@ func TestConfig_Patch_Response(t *testing.T) { name: "add content type schema", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", openapitest.NewContent()))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/plain"))), + ), + ), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", - openapitest.NewContent( - openapitest.WithSchema(schematest.New("number")))))), - ), - ))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/plain", + openapitest.WithSchema(schematest.New("number"))))), + ), + ), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) @@ -589,21 +588,18 @@ func TestConfig_Patch_Response(t *testing.T) { name: "patch content type schema", configs: []*openapi.Config{ openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", - openapitest.NewContent( - openapitest.WithSchema(schematest.New("number")))), - ), - ))))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/plain", + + openapitest.WithSchema(schematest.New("number")))), + ))), openapitest.NewConfig("1.0", openapitest.WithPath( - "/foo", openapitest.NewPath(openapitest.WithOperation( - "post", openapitest.NewOperation( - openapitest.WithResponse(200, openapitest.WithContent("text/plain", - openapitest.NewContent( - openapitest.WithSchema(schematest.New("number", schematest.WithFormat("double")))), - ), - )))))), + "/foo", openapitest.WithOperation( + "post", + openapitest.WithResponse(200, openapitest.WithContent("text/plain", + openapitest.WithSchema(schematest.New("number", schematest.WithFormat("double")))), + )))), }, test: func(t *testing.T, result *openapi.Config) { res := result.Paths["/foo"].Value.Post.Responses.GetResponse(200) diff --git a/providers/openapi/schema/apply.go b/providers/openapi/schema/apply.go index 311ca87d4..4f12e8abe 100644 --- a/providers/openapi/schema/apply.go +++ b/providers/openapi/schema/apply.go @@ -55,16 +55,22 @@ func (s *Schema) apply(ref *Schema) { } if !s.isSet("items") { - s.Items = ref.Items + resolved := cloneSchema(ref.Items) + s.Items = resolved } if !s.isSet("prefixItems") { - s.PrefixItems = ref.PrefixItems + for _, pi := range ref.PrefixItems { + resolved := cloneSchema(pi) + s.PrefixItems = append(s.PrefixItems, resolved) + } } if !s.isSet("unevaluatedItems") { - s.UnevaluatedItems = ref.UnevaluatedItems + resolved := cloneSchema(ref.UnevaluatedItems) + s.UnevaluatedItems = resolved } if !s.isSet("contains") { - s.Contains = ref.Contains + resolved := cloneSchema(ref.Contains) + s.Contains = resolved } if !s.isSet("maxContains") { s.MaxContains = ref.MaxContains @@ -88,11 +94,19 @@ func (s *Schema) apply(ref *Schema) { s.ShuffleItems = ref.ShuffleItems } - if !s.isSet("properties") { - s.Properties = ref.Properties + if !s.isSet("properties") && ref.Properties != nil { + s.Properties = &Schemas{} + for it := ref.Properties.Iter(); it.Next(); { + resolved := cloneSchema(it.Value()) + s.Properties.Set(it.Key(), resolved) + } } if !s.isSet("patternProperties") { - s.PatternProperties = ref.PatternProperties + s.PatternProperties = map[string]*Schema{} + for name, pi := range ref.PatternProperties { + resolved := cloneSchema(pi) + s.PatternProperties[name] = resolved + } } if !s.isSet("minProperties") { s.MinProperties = ref.MinProperties @@ -115,10 +129,12 @@ func (s *Schema) apply(ref *Schema) { fmt.Print("") } if !s.isSet("unevaluatedProperties") { - s.UnevaluatedProperties = ref.UnevaluatedProperties + resolved := cloneSchema(ref.UnevaluatedProperties) + s.UnevaluatedProperties = resolved } if !s.isSet("propertyNames") { - s.PropertyNames = ref.PropertyNames + resolved := cloneSchema(ref.PropertyNames) + s.PropertyNames = resolved } if !s.isSet("anyOf") { @@ -190,3 +206,11 @@ func (s *Schema) isEmpty() bool { func (s *Schema) isSet(name string) bool { return s.m[name] } + +func cloneSchema(s *Schema) *Schema { + if s == nil { + return nil + } + c := *s + return &c +} diff --git a/providers/openapi/schema/patch.go b/providers/openapi/schema/patch.go index 0f1c1c8bf..c6ddf277c 100644 --- a/providers/openapi/schema/patch.go +++ b/providers/openapi/schema/patch.go @@ -175,8 +175,6 @@ func (s *Schema) Patch(patch *Schema) { } } } - - s.cm.Notify(s) } func (x *Xml) patch(patch *Xml) { diff --git a/providers/openapi/schema/schema.go b/providers/openapi/schema/schema.go index 82bc7beff..4fae84909 100644 --- a/providers/openapi/schema/schema.go +++ b/providers/openapi/schema/schema.go @@ -91,7 +91,6 @@ type Schema struct { Sub *Schema `yaml:"-" json:"-"` m map[string]bool - cm changeManager } func (s *Schema) HasProperties() bool { @@ -99,91 +98,123 @@ func (s *Schema) HasProperties() bool { } func (s *Schema) Parse(config *dynamic.Config, reader dynamic.Reader) error { + p := &schemaParser{visited: map[*Schema]bool{}, config: config, reader: reader} + return p.parse(s) +} + +type schemaParser struct { + visited map[*Schema]bool + config *dynamic.Config + reader dynamic.Reader +} + +func (p *schemaParser) parse(s *Schema) error { if s == nil { return nil } + if p.visited[s] { + return nil + } + p.visited[s] = true + + if s.Id != "" { + p.config.OpenScope(s.Id) + defer p.config.CloseScope() + } else { + p.config.Scope.OpenIfNeeded(p.config.Info.Path()) + } + for _, d := range s.Definitions { - if err := d.Parse(config, reader); err != nil { + if err := p.parse(d); err != nil { return err } } for _, d := range s.Defs { - if err := d.Parse(config, reader); err != nil { + if err := p.parse(d); err != nil { return err } } - if s.Id != "" { - config.OpenScope(s.Id) - defer config.CloseScope() - } else { - config.Scope.OpenIfNeeded(config.Info.Path()) - } - if s.Anchor != "" { - if err := config.Scope.SetLexical(s.Anchor, s); err != nil { + if err := p.config.Scope.SetLexical(s.Anchor, s); err != nil { return err } } if s.DynamicAnchor != "" { - if err := config.Scope.SetDynamic(s.DynamicAnchor, s); err != nil { + if err := p.config.Scope.SetDynamic(s.DynamicAnchor, s); err != nil { return err } } - if err := s.Items.Parse(config, reader); err != nil { - return err + if !s.skipParse("items") { + if err := p.parse(s.Items); err != nil { + return err + } } - if err := s.Properties.Parse(config, reader); err != nil { - return err + if s.Properties != nil && !s.skipParse("properties") { + for it := s.Properties.Iter(); it.Next(); { + if err := p.parse(it.Value()); err != nil { + return fmt.Errorf("parse schema '%v' failed: %w", it.Key(), err) + } + } } - if err := s.AdditionalProperties.Parse(config, reader); err != nil { - return err + if !s.skipParse("additionalProperties") { + if err := p.parse(s.AdditionalProperties); err != nil { + return err + } } - for _, r := range s.AnyOf { - if err := r.Parse(config, reader); err != nil { - return err + if !s.skipParse("anyOf") { + for _, r := range s.AnyOf { + if err := p.parse(r); err != nil { + return err + } } } - for _, r := range s.AllOf { - if err := r.Parse(config, reader); err != nil { - return err + if !s.skipParse("allOf") { + for _, r := range s.AllOf { + if err := p.parse(r); err != nil { + return err + } } } - for _, r := range s.OneOf { - if err := r.Parse(config, reader); err != nil { - return err + if !s.skipParse("oneOf") { + for _, r := range s.OneOf { + if err := p.parse(r); err != nil { + return err + } } } if s.Ref != "" { - err := dynamic.Resolve(s.Ref, &s.Sub, config, reader) + err := dynamic.Resolve(s.Ref, &s.Sub, p.config, p.reader) if err != nil { return err } + + // Apply the resolved schema as an overlay onto the current schema. + // The referenced schema is cloned to preserve the immutability of + // the parsed schema graph. Dynamic references may resolve differently + // depending on the evaluation context, so shared schema nodes must + // never be mutated. s.apply(s.Sub) - s.Sub.cm.Subscribe(s.apply) } if s.DynamicRef != "" { - err := dynamic.ResolveDynamic(s.DynamicRef, &s.Sub, config, reader) + err := dynamic.ResolveDynamic(s.DynamicRef, &s.Sub, p.config, p.reader) if err != nil { return err } s.apply(s.Sub) - s.Sub.cm.Subscribe(s.apply) } - s.cm.Notify(s) - return nil } @@ -306,7 +337,7 @@ func (s *Schema) String() string { sb.WriteString(fmt.Sprintf(" description=%v", s.Description)) } - return sb.String() + return strings.TrimSpace(sb.String()) } func (s *Schema) IsFreeForm() bool { @@ -394,3 +425,11 @@ func (s *Schema) UnmarshalYAML(node *yaml.Node) error { *s = Schema(a) return nil } + +func (s *Schema) skipParse(name string) bool { + if s.Ref != "" { + _, ok := s.m[name] + return !ok + } + return false +} diff --git a/providers/openapi/schema/schema_parse_test.go b/providers/openapi/schema/schema_parse_test.go index 74e32a0e9..4a1da99e5 100644 --- a/providers/openapi/schema/schema_parse_test.go +++ b/providers/openapi/schema/schema_parse_test.go @@ -1,13 +1,15 @@ package schema_test import ( - "github.com/stretchr/testify/require" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/providers/openapi/schema" "mokapi/providers/openapi/schema/schematest" jsonSchema "mokapi/schema/json/schema" "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestJson_Structuring(t *testing.T) { @@ -149,6 +151,36 @@ func TestJson_Structuring(t *testing.T) { require.Equal(t, "string", s.Properties.Get("first_name").Type.String()) }, }, + { + name: "$ref using in referenced file", + test: func(t *testing.T) { + reader := &dynamictest.Reader{ + Data: map[string]*dynamic.Config{ + "/bar.json": { + Info: dynamictest.NewConfigInfo(dynamictest.WithUrl("/bar.json")), + Raw: []byte(`{ +"$defs": { "items": { "type": "integer" } }, +"type": "array", +"items": { "$ref": "#/$defs/items" } +}`), + }, + }, + } + + foo := &dynamic.Config{ + Info: dynamictest.NewConfigInfo(dynamictest.WithUrl("/foo.json")), + Data: &schema.Schema{ + Ref: "/bar.json", + }, + } + + err := foo.Data.(*schema.Schema).Parse(foo, reader) + require.NoError(t, err) + + require.NoError(t, err) + require.Equal(t, "integer", foo.Data.(*schema.Schema).Items.Type.String()) + }, + }, { name: "recursion", test: func(t *testing.T) { @@ -166,6 +198,26 @@ func TestJson_Structuring(t *testing.T) { require.Equal(t, s, children.Items.Sub) }, }, + { + name: "recursion using $def", + test: func(t *testing.T) { + data := ` +$defs: + a: + $ref: #/$defs/b + b: + $ref: '#' +$ref: '#/$defs/a' +` + var s *schema.Schema + err := yaml.Unmarshal([]byte(data), &s) + require.NoError(t, err) + + err = s.Parse(&dynamic.Config{Data: s}, &dynamictest.Reader{}) + require.NoError(t, err) + require.Equal(t, "", s.String()) + }, + }, { name: "generic list of strings", test: func(t *testing.T) { @@ -202,6 +254,40 @@ func TestJson_Structuring(t *testing.T) { require.Equal(t, "string", person.Data.(*schema.Schema).Items.Type.String()) }, }, + { + name: "parsing twice with $refs", + test: func(t *testing.T) { + reader := &dynamictest.Reader{ + Data: map[string]*dynamic.Config{ + "https://example.com/schemas/foo": { + Info: dynamictest.NewConfigInfo(dynamictest.WithUrl("https://example.com/schemas/foo")), + Raw: []byte(`{ +"$defs": { "items": { "type": "integer" } }, +"type": "array", +"items": { "$ref": "#/$defs/items" }, +}`), + }, + }, + } + + person := &dynamic.Config{ + Info: dynamictest.NewConfigInfo(dynamictest.WithUrl("https://example.com/schemas/bar")), + Data: &schema.Schema{ + Ref: "https://example.com/schemas/foo", + }, + } + + // 1. parsing + err := person.Data.(*schema.Schema).Parse(person, reader) + require.NoError(t, err) + + // 2. parsing + err = person.Data.(*schema.Schema).Parse(person, reader) + + require.NoError(t, err) + require.Equal(t, "integer", person.Data.(*schema.Schema).Items.Type.String()) + }, + }, } t.Parallel() diff --git a/providers/openapi/schema/schemas.go b/providers/openapi/schema/schemas.go index 289eff906..eb6b448e8 100644 --- a/providers/openapi/schema/schemas.go +++ b/providers/openapi/schema/schemas.go @@ -4,30 +4,17 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/pkg/errors" - "gopkg.in/yaml.v3" "mokapi/config/dynamic" "mokapi/sortedmap" + + "github.com/pkg/errors" + "gopkg.in/yaml.v3" ) type Schemas struct { sortedmap.LinkedHashMap[string, *Schema] } -func (s *Schemas) Parse(config *dynamic.Config, reader dynamic.Reader) error { - if s == nil { - return nil - } - - for it := s.Iter(); it.Next(); { - if err := it.Value().Parse(config, reader); err != nil { - return fmt.Errorf("parse schema '%v' failed: %w", it.Key(), err) - } - } - - return nil -} - func (s *Schemas) Get(name string) *Schema { if s == nil { return nil diff --git a/runtime/index_test.go b/runtime/index_test.go index dd918b623..01e5e816c 100644 --- a/runtime/index_test.go +++ b/runtime/index_test.go @@ -34,11 +34,11 @@ func TestIndex(t *testing.T) { require.NoError(t, err) petstore := openapitest.NewConfig("3.0", openapitest.WithInfo("Swagger Petstore", "1.0", "This is a sample server Petstore server. You can find out more about Swagger at https://swagger.io"), - openapitest.WithPath("/pet", openapitest.NewPath( - openapitest.WithOperation("put", openapitest.NewOperation( + openapitest.WithPath("/pet", + openapitest.WithOperation("put", openapitest.WithOperationInfo("Update an existing pet.", "Update an existing pet by Id.", "updatePet", false), - )), - )), + ), + ), ) app.AddHttp(&dynamic.Config{ Info: dynamic.ConfigInfo{Url: try.MustUrl("petstore.yaml"), Provider: "NPM"}, diff --git a/runtime/runtime_http_search_test.go b/runtime/runtime_http_search_test.go index 74494a172..365afeab4 100644 --- a/runtime/runtime_http_search_test.go +++ b/runtime/runtime_http_search_test.go @@ -147,7 +147,7 @@ func TestIndex_Http(t *testing.T) { test: func(t *testing.T, app *runtime.App) { cfg := openapitest.NewConfig("3.0", openapitest.WithInfo("foo", "1.0", ""), - openapitest.WithPath("/pets", openapitest.NewPath()), + openapitest.WithPath("/pets"), ) app.AddHttp(toConfig(cfg)) @@ -179,7 +179,7 @@ func TestIndex_Http(t *testing.T) { test: func(t *testing.T, app *runtime.App) { cfg := openapitest.NewConfig("3.0", openapitest.WithInfo("foo", "1.0", "a description"), - openapitest.WithPath("/pets", openapitest.NewPath(openapitest.WithPathInfo("", "a description"))), + openapitest.WithPath("/pets", openapitest.WithPathInfo("", "a description")), ) app.AddHttp(toConfig(cfg)) @@ -222,12 +222,12 @@ func TestIndex_Http(t *testing.T) { test: func(t *testing.T, app *runtime.App) { cfg := openapitest.NewConfig("3.0", openapitest.WithInfo("foo", "1.0", "a description"), - openapitest.WithPath("/pets", openapitest.NewPath( + openapitest.WithPath("/pets", openapitest.WithPathInfo("", "a description"), - openapitest.WithOperation("get", openapitest.NewOperation( + openapitest.WithOperation("get", openapitest.WithHeaderParam("foo", true, openapitest.WithParamInfo("parameter description")), - )), - )), + ), + ), ) app.AddHttp(toConfig(cfg)) @@ -259,12 +259,12 @@ func TestIndex_Http(t *testing.T) { name: "Search by api field", test: func(t *testing.T, app *runtime.App) { cfg := openapitest.NewConfig("3.0", openapitest.WithInfo("Petstore", "", ""), - openapitest.WithPath("/pets", openapitest.NewPath( + openapitest.WithPath("/pets", openapitest.WithPathInfo("", "path"), - openapitest.WithOperation("get", openapitest.NewOperation( + openapitest.WithOperation("get", openapitest.WithHeaderParam("foo", true, openapitest.WithParamInfo("parameter")), - )), - )), + ), + ), ) app.AddHttp(toConfig(cfg)) // search response should only have one the root OpenAPI object @@ -325,12 +325,12 @@ func TestIndex_Http(t *testing.T) { name: "Search by api field", test: func(t *testing.T, app *runtime.App) { cfg := openapitest.NewConfig("3.0", openapitest.WithInfo("Petstore", "", ""), - openapitest.WithPath("/pets", openapitest.NewPath( + openapitest.WithPath("/pets", openapitest.WithPathInfo("", "path"), - openapitest.WithOperation("get", openapitest.NewOperation( + openapitest.WithOperation("get", openapitest.WithHeaderParam("foo", true, openapitest.WithParamInfo("parameter")), - )), - )), + ), + ), ) app.AddHttp(toConfig(cfg)) // search response should only have one the root OpenAPI object @@ -364,12 +364,12 @@ func TestIndex_Http(t *testing.T) { name: "Search fuzzy", test: func(t *testing.T, app *runtime.App) { cfg := openapitest.NewConfig("3.0", openapitest.WithInfo("Petstore", "", ""), - openapitest.WithPath("/pets", openapitest.NewPath( + openapitest.WithPath("/pets", openapitest.WithPathInfo("", "path"), - openapitest.WithOperation("get", openapitest.NewOperation( + openapitest.WithOperation("get", openapitest.WithHeaderParam("foo", true, openapitest.WithParamInfo("parameter")), - )), - )), + ), + ), ) app.AddHttp(toConfig(cfg)) // search response should only have one the root OpenAPI object diff --git a/runtime/runtime_http_test.go b/runtime/runtime_http_test.go index c65a8ec95..bac8a8f6c 100644 --- a/runtime/runtime_http_test.go +++ b/runtime/runtime_http_test.go @@ -1,7 +1,6 @@ package runtime_test import ( - "github.com/stretchr/testify/require" "mokapi/config/dynamic" "mokapi/config/static" "mokapi/engine/enginetest" @@ -15,6 +14,8 @@ import ( "net/http/httptest" "net/url" "testing" + + "github.com/stretchr/testify/require" ) func TestApp_AddHttp(t *testing.T) { @@ -36,7 +37,7 @@ func TestApp_AddHttp(t *testing.T) { name: "event store for endpoint available", test: func(t *testing.T, app *runtime.App) { app.AddHttp(newConfig(openapitest.NewConfig("3.0", openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("bar", openapitest.NewPath())))) + openapitest.WithPath("bar")))) require.NotNil(t, app.GetHttp("foo")) err := app.Events.Push(&eventstest.Event{Name: "bar"}, events.NewTraits().WithNamespace("http").WithName("foo").With("path", "bar")) @@ -47,11 +48,11 @@ func TestApp_AddHttp(t *testing.T) { name: "request is counted in monitor", test: func(t *testing.T, app *runtime.App) { info := app.AddHttp(newConfig(openapitest.NewConfig("3.0", openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("/foo", openapitest.NewPath( - openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithPath("/foo", + openapitest.WithOperation(http.MethodGet, openapitest.WithResponse(http.StatusOK), - )))), - ))) + ))), + )) m := monitor.NewHttp() h := info.Handler(m, enginetest.NewEngine(), &events.StoreManager{}) @@ -66,7 +67,7 @@ func TestApp_AddHttp(t *testing.T) { name: "retrieve configs", test: func(t *testing.T, app *runtime.App) { info := app.AddHttp(newConfig(openapitest.NewConfig("3.0", openapitest.WithInfo("foo", "", ""), - openapitest.WithPath("bar", openapitest.NewPath())))) + openapitest.WithPath("bar")))) configs := info.Configs() require.Len(t, configs, 1) diff --git a/schema/json/schema/apply.go b/schema/json/schema/apply.go index a4aa4953e..64507bd8e 100644 --- a/schema/json/schema/apply.go +++ b/schema/json/schema/apply.go @@ -1,144 +1,187 @@ package schema -import "strings" +import ( + "fmt" + "strings" +) func (s *Schema) apply(ref *Schema) { if ref == nil { return } - if len(s.m) == 0 { - *s = *ref - return - } if s.isEmpty() && s.Boolean == nil && ref.Boolean != nil { s.Boolean = ref.Boolean return } - if len(s.Type) == 0 { + if !s.isSet("type") { s.Type = ref.Type } - if s.Enum == nil { + if !s.isSet("enum") { s.Enum = ref.Enum } - if s.Const == nil { + if !s.isSet("const") { s.Const = ref.Const } - if len(s.Format) == 0 { - s.Format = ref.Format + + if !s.isSet("multipleOf") { + s.MultipleOf = ref.MultipleOf + } + if !s.isSet("minimum") { + s.Minimum = ref.Minimum + } + if !s.isSet("maximum") { + s.Maximum = ref.Maximum + } + if !s.isSet("exclusiveMinimum") { + s.ExclusiveMinimum = ref.ExclusiveMinimum + } + if !s.isSet("exclusiveMaximum") { + s.ExclusiveMaximum = ref.ExclusiveMaximum } - if len(s.Pattern) == 0 { + if !s.isSet("pattern") { s.Pattern = ref.Pattern } - if s.MinLength == nil { + if !s.isSet("minLength") { s.MinLength = ref.MinLength } - if s.MaxLength == nil { + if !s.isSet("maxLength") { s.MaxLength = ref.MaxLength } - if s.MultipleOf == nil { - s.MultipleOf = ref.MultipleOf + if !s.isSet("format") { + s.Format = ref.Format } - if s.Minimum == nil { - s.Minimum = ref.Minimum + + if !s.isSet("items") { + resolved := cloneSchema(ref.Items) + s.Items = resolved } - if s.Maximum == nil { - s.Maximum = ref.Maximum + if !s.isSet("prefixItems") { + for _, pi := range ref.PrefixItems { + resolved := cloneSchema(pi) + s.PrefixItems = append(s.PrefixItems, resolved) + } } - if s.ExclusiveMinimum == nil { - s.ExclusiveMinimum = ref.ExclusiveMinimum + if !s.isSet("unevaluatedItems") { + resolved := cloneSchema(ref.UnevaluatedItems) + s.UnevaluatedItems = resolved } - if s.ExclusiveMaximum == nil { - s.ExclusiveMaximum = ref.ExclusiveMaximum + if !s.isSet("contains") { + resolved := cloneSchema(ref.Contains) + s.Contains = resolved } - if s.Items == nil { - s.Items = ref.Items + if !s.isSet("maxContains") { + s.MaxContains = ref.MaxContains } - - if _, ok := s.m["uniqueItems"]; !ok { - s.UniqueItems = ref.UniqueItems + if !s.isSet("minContains") { + s.MinContains = ref.MinContains } - - if s.MinItems == nil { + if !s.isSet("minContains") { + s.MinContains = ref.MinContains + } + if !s.isSet("minItems") { s.MinItems = ref.MinItems } - if s.MaxItems == nil { + if !s.isSet("maxItems") { s.MaxItems = ref.MaxItems } - - if _, ok := s.m["shuffleItems"]; !ok { + if !s.isSet("uniqueItems") { + s.UniqueItems = ref.UniqueItems + } + if !s.isSet("shuffleItems") { s.ShuffleItems = ref.ShuffleItems } - if s.Properties == nil { - s.Properties = ref.Properties + if !s.isSet("properties") && ref.Properties != nil { + s.Properties = &Schemas{} + for it := ref.Properties.Iter(); it.Next(); { + resolved := cloneSchema(it.Value()) + s.Properties.Set(it.Key(), resolved) + } } - - if s.Required == nil { + if !s.isSet("patternProperties") { + s.PatternProperties = map[string]*Schema{} + for name, pi := range ref.PatternProperties { + resolved := cloneSchema(pi) + s.PatternProperties[name] = resolved + } + } + if !s.isSet("minProperties") { + s.MinProperties = ref.MinProperties + } + if !s.isSet("maxProperties") { + s.MaxProperties = ref.MaxProperties + } + if !s.isSet("required") { s.Required = ref.Required } - - if s.AdditionalProperties == nil { + if !s.isSet("dependentRequired") { + s.DependentRequired = ref.DependentRequired + } + if !s.isSet("dependentSchemas") { + s.DependentSchemas = ref.DependentSchemas + } + if !s.isSet("additionalProperties") { s.AdditionalProperties = ref.AdditionalProperties } else { - s.AdditionalProperties.apply(ref.AdditionalProperties) + fmt.Print("") + } + if !s.isSet("unevaluatedProperties") { + resolved := cloneSchema(ref.UnevaluatedProperties) + s.UnevaluatedProperties = resolved + } + if !s.isSet("propertyNames") { + resolved := cloneSchema(ref.PropertyNames) + s.PropertyNames = resolved } - if s.MinProperties == nil { - s.MinProperties = ref.MinProperties + if !s.isSet("anyOf") { + s.AnyOf = ref.AnyOf } - if s.MaxProperties == nil { - s.MaxProperties = ref.MaxProperties + if !s.isSet("allOf") { + s.AllOf = ref.AllOf + } + if !s.isSet("oneOf") { + s.OneOf = ref.OneOf + } + if !s.isSet("not") { + s.Not = ref.Not + } + + if !s.isSet("if") { + s.If = ref.If + } + if !s.isSet("then") { + s.Then = ref.Then } - if len(s.Title) == 0 { + if !s.isSet("else") { + s.Else = ref.Else + } + + if !s.isSet("title") { s.Title = ref.Title } - if len(s.Description) == 0 { + if !s.isSet("description") { s.Description = ref.Description } - if s.Default == nil { + if !s.isSet("default") { s.Default = ref.Default } - - if _, ok := s.m["deprecated"]; !ok { + if !s.isSet("deprecated") { s.Deprecated = ref.Deprecated } - - if s.Examples == nil { + if !s.isSet("examples") { s.Examples = ref.Examples } - if len(s.ContentMediaType) == 0 { + + if !s.isSet("contentMediaType") { s.ContentMediaType = ref.ContentMediaType } - if len(s.ContentEncoding) == 0 { + if !s.isSet("contentEncoding") { s.ContentEncoding = ref.ContentEncoding } - - if s.Definitions == nil { - s.Definitions = ref.Definitions - } else { - for k, v := range ref.Definitions { - if def, ok := s.Definitions[k]; ok { - def.apply(v) - } else { - s.Definitions[k] = v - } - } - } - - if s.Defs == nil { - s.Defs = ref.Defs - } else { - for k, v := range ref.Defs { - if def, ok := s.Defs[k]; ok { - def.apply(v) - } else { - s.Defs[k] = v - } - } - } } func (s *Schema) isEmpty() bool { @@ -149,3 +192,15 @@ func (s *Schema) isEmpty() bool { } return true } + +func (s *Schema) isSet(name string) bool { + return s.m[name] +} + +func cloneSchema(s *Schema) *Schema { + if s == nil { + return nil + } + c := *s + return &c +} diff --git a/schema/json/schema/schema.go b/schema/json/schema/schema.go index 02454098f..c4767d069 100644 --- a/schema/json/schema/schema.go +++ b/schema/json/schema/schema.go @@ -3,8 +3,9 @@ package schema import ( "encoding/json" "fmt" - "gopkg.in/yaml.v3" "mokapi/config/dynamic" + + "gopkg.in/yaml.v3" ) type Schema struct { @@ -151,7 +152,14 @@ func (s *Schema) Validate() error { } func (s *Schema) UnmarshalJSON(b []byte) error { - _ = json.Unmarshal(b, &s.m) + m := map[string]json.RawMessage{} + _ = json.Unmarshal(b, &m) + if s.m == nil { + s.m = map[string]bool{} + } + for k := range m { + s.m[k] = true + } var boolVal bool if err := json.Unmarshal(b, &boolVal); err == nil { @@ -176,7 +184,14 @@ func (s *Schema) UnmarshalJSON(b []byte) error { } func (s *Schema) UnmarshalYAML(node *yaml.Node) error { - _ = node.Decode(&s.m) + m := map[string]yaml.Node{} + _ = node.Decode(&m) + if s.m == nil { + s.m = map[string]bool{} + } + for k := range m { + s.m[k] = true + } var boolVal bool if err := node.Decode(&boolVal); err == nil { @@ -211,34 +226,34 @@ func (s *Schema) Parse(config *dynamic.Config, reader dynamic.Reader) error { config.Scope.OpenIfNeeded(config.Info.Path()) } - if s.Anchor != "" { - if err := config.Scope.SetLexical(s.Anchor, s); err != nil { + for _, d := range s.Definitions { + if err := d.Parse(config, reader); err != nil { return err } } - if s.DynamicAnchor != "" { - if err := config.Scope.SetDynamic(s.DynamicAnchor, s); err != nil { + for _, d := range s.Defs { + if err := d.Parse(config, reader); err != nil { return err } } - if err := s.Validate(); err != nil { - return err - } - - for _, d := range s.Definitions { - if err := d.Parse(config, reader); err != nil { + if s.Anchor != "" { + if err := config.Scope.SetLexical(s.Anchor, s); err != nil { return err } } - for _, d := range s.Defs { - if err := d.Parse(config, reader); err != nil { + if s.DynamicAnchor != "" { + if err := config.Scope.SetDynamic(s.DynamicAnchor, s); err != nil { return err } } + if err := s.Validate(); err != nil { + return err + } + if err := s.Items.Parse(config, reader); err != nil { return err } diff --git a/schema/json/schema/schema_json_test.go b/schema/json/schema/schema_json_test.go index d256dd1a4..70ee2df00 100644 --- a/schema/json/schema/schema_json_test.go +++ b/schema/json/schema/schema_json_test.go @@ -2,12 +2,13 @@ package schema_test import ( "encoding/json" - "github.com/stretchr/testify/require" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/schema/json/schema" "mokapi/schema/json/schema/schematest" "testing" + + "github.com/stretchr/testify/require" ) func TestSchemaJson(t *testing.T) { @@ -475,17 +476,21 @@ func TestJson_Structuring(t *testing.T) { { name: "recursion", test: func(t *testing.T) { - s := schematest.New("object", + entry := schematest.New("object", schematest.WithProperty("name", schematest.New("string")), schematest.WithProperty("children", schematest.New("array", schematest.WithItemsRefString("#")), ), ) - err := s.Parse(&dynamic.Config{Data: s}, &dynamictest.Reader{}) + err := entry.Parse(&dynamic.Config{Data: entry}, &dynamictest.Reader{}) require.NoError(t, err) - children := s.Properties.Get("children") + // entry does not contain $ref=# + // only the next level is identical + children := entry.Properties.Get("children") + s := children.Items + children = s.Properties.Get("children") require.Equal(t, s, children.Items) }, }, diff --git a/schema/json/schema/schema_yaml_test.go b/schema/json/schema/schema_yaml_test.go index 09ed12d7f..28d7abfcb 100644 --- a/schema/json/schema/schema_yaml_test.go +++ b/schema/json/schema/schema_yaml_test.go @@ -1,12 +1,13 @@ package schema_test import ( - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/schema/json/schema" "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) func TestSchemaYaml(t *testing.T) { diff --git a/server/httpmanager_test.go b/server/httpmanager_test.go index 58779965c..eb27c84bb 100644 --- a/server/httpmanager_test.go +++ b/server/httpmanager_test.go @@ -35,7 +35,7 @@ func TestHttpServers_Monitor(t *testing.T) { port := try.GetFreePort() u := fmt.Sprintf("http://localhost:%v", port) c := openapitest.NewConfig("3.0", openapitest.WithInfo("test", "1.0", ""), openapitest.WithServer(u, "")) - openapitest.AppendPath("/foo", c, openapitest.WithOperation("get", openapitest.NewOperation())) + openapitest.AppendPath("/foo", c, openapitest.WithOperation("get")) //c := &openapi.Config{OpenApi: "3.0", Info: openapi.Info{Name: "foo"}, Servers: []*openapi.Server{{Url: url}}} m.Update(dynamic.ConfigEvent{Config: &dynamic.Config{Info: dynamic.ConfigInfo{Url: MustParseUrl("foo.yml")}, Data: c}}) diff --git a/server/server_http_test.go b/server/server_http_test.go index 4809b4f79..119de3717 100644 --- a/server/server_http_test.go +++ b/server/server_http_test.go @@ -57,10 +57,10 @@ func TestHttp(t *testing.T) { Config: &dynamic.Config{ Info: dynamictest.NewConfigInfo(), Data: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( - openapitest.WithResponse(200))), - )), + openapitest.WithPath("/", + openapitest.WithOperation("GET", + openapitest.WithResponse(200)), + ), ), }, }) @@ -79,10 +79,10 @@ func TestHttp(t *testing.T) { Config: &dynamic.Config{ Info: dynamictest.NewConfigInfo(), Data: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( - openapitest.WithResponse(200))), - )), + openapitest.WithPath("/", + openapitest.WithOperation("GET", + openapitest.WithResponse(200)), + ), ), }, }) @@ -116,10 +116,10 @@ func TestHttp(t *testing.T) { Config: &dynamic.Config{ Info: dynamictest.NewConfigInfo(), Data: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( - openapitest.WithResponse(200))), - )), + openapitest.WithPath("/", + openapitest.WithOperation("GET", + openapitest.WithResponse(200)), + ), openapitest.WithServer("/", ""), ), }, @@ -128,10 +128,10 @@ func TestHttp(t *testing.T) { Config: &dynamic.Config{ Info: dynamictest.NewConfigInfo(), Data: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( - openapitest.WithResponse(200))), - )), + openapitest.WithPath("/", + openapitest.WithOperation("GET", + openapitest.WithResponse(200)), + ), openapitest.WithServer("/foo", ""), ), }, @@ -156,10 +156,10 @@ func TestHttp(t *testing.T) { Config: &dynamic.Config{ Info: dynamictest.NewConfigInfo(), Data: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( - openapitest.WithResponse(200))), - )), + openapitest.WithPath("/", + openapitest.WithOperation("GET", + openapitest.WithResponse(200)), + ), openapitest.WithServer("/foo", ""), ), }, @@ -169,10 +169,10 @@ func TestHttp(t *testing.T) { Config: &dynamic.Config{ Info: dynamictest.NewConfigInfo(), Data: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( - openapitest.WithResponse(200))), - )), + openapitest.WithPath("/", + openapitest.WithOperation("GET", + openapitest.WithResponse(200)), + ), openapitest.WithServer(fmt.Sprintf("http://:%v/foo", port), ""), ), }, @@ -192,10 +192,10 @@ func TestHttp(t *testing.T) { Config: &dynamic.Config{ Info: dynamictest.NewConfigInfo(), Data: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( - openapitest.WithResponse(200))), - )), + openapitest.WithPath("/", + openapitest.WithOperation("GET", + openapitest.WithResponse(200)), + ), openapitest.WithServer("/foo", ""), ), }, @@ -205,10 +205,10 @@ func TestHttp(t *testing.T) { Config: &dynamic.Config{ Info: dynamictest.NewConfigInfo(), Data: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( - openapitest.WithResponse(200))), - )), + openapitest.WithPath("/", + openapitest.WithOperation("GET", + openapitest.WithResponse(200)), + ), openapitest.WithServer("/foo", ""), ), }, @@ -233,13 +233,13 @@ func TestHttp(t *testing.T) { Config: &dynamic.Config{ Info: dynamictest.NewConfigInfo(), Data: openapitest.NewConfig("3.1.0", - openapitest.WithPath("/", openapitest.NewPath( - openapitest.WithOperation("GET", openapitest.NewOperation( + openapitest.WithPath("/", + openapitest.WithOperation("GET", openapitest.WithResponse(200, openapitest.WithContent( "application/json", - openapitest.NewContent(openapitest.WithSchema(schematest.New("string", schematest.WithConst("foo")))), - )))), - )), + openapitest.WithSchema(schematest.New("string", schematest.WithConst("foo")))), + )), + ), ), }, }) diff --git a/webui/package-lock.json b/webui/package-lock.json index 82c311558..a35294dd7 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -11,8 +11,8 @@ "@popperjs/core": "^2.11.6", "@ssthouse/vue3-tree-chart": "^0.3.0", "@types/bootstrap": "^5.2.10", - "@types/mokapi": "^0.34.0", - "@types/nodemailer": "^7.0.10", + "@types/mokapi": "^0.35.0", + "@types/nodemailer": "^7.0.11", "@types/whatwg-mimetype": "^5.0.0", "ace-builds": "^1.43.5", "bootstrap": "^5.3.8", @@ -47,14 +47,14 @@ "@vitejs/plugin-vue": "^6.0.4", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.7.0", - "@vue/tsconfig": "^0.8.1", - "eslint": "^10.0.0", + "@vue/tsconfig": "^0.9.0", + "eslint": "^10.0.2", "eslint-plugin-vue": "^10.8.0", "npm-run-all": "^4.1.5", "prettier": "^3.8.1", "typescript": "~5.9.3", "vite": "^7.3.1", - "vue-tsc": "^3.2.4", + "vue-tsc": "^3.2.5", "xml2js": "^0.6.2" } }, @@ -1329,9 +1329,9 @@ "license": "MIT" }, "node_modules/@types/mokapi": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/@types/mokapi/-/mokapi-0.34.0.tgz", - "integrity": "sha512-AtSfdd/aZDhIaKYdtyq5v05JuJijQhMVVu1XuIosnkCtkRerK1JIm/k6ibnJy6Taxez+ztSLgYdEd1ij05YORQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@types/mokapi/-/mokapi-0.35.0.tgz", + "integrity": "sha512-Q6hUjeh6RIjHG0wjG8hrlNfPdc2ZBC77gC4D70ngEDgq6BWddVT079cobc79JfxLsNhZL4sPMiNWDC1te66GAQ==", "license": "MIT" }, "node_modules/@types/node": { @@ -1344,9 +1344,9 @@ } }, "node_modules/@types/nodemailer": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.10.tgz", - "integrity": "sha512-tP+9WggTFN22Zxh0XFyst7239H0qwiRCogsk7v9aQS79sYAJY+WEbTHbNYcxUMaalHKmsNpxmoTe35hBEMMd6g==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.11.tgz", + "integrity": "sha512-E+U4RzR2dKrx+u3N4DlsmLaDC6mMZOM/TPROxA0UAPiTgI0y4CEFBmZE+coGWTjakDriRsXG368lNk1u9Q0a2g==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -1619,30 +1619,30 @@ } }, "node_modules/@volar/language-core": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", - "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", "dev": true, "license": "MIT", "dependencies": { - "@volar/source-map": "2.4.27" + "@volar/source-map": "2.4.28" } }, "node_modules/@volar/source-map": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", - "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", "dev": true, "license": "MIT" }, "node_modules/@volar/typescript": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", - "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.27", + "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } @@ -1799,13 +1799,13 @@ } }, "node_modules/@vue/language-core": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.4.tgz", - "integrity": "sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.5.tgz", + "integrity": "sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.27", + "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", @@ -1878,9 +1878,9 @@ "license": "MIT" }, "node_modules/@vue/tsconfig": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.8.1.tgz", - "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.0.tgz", + "integrity": "sha512-RP+v9Cpbsk1ZVXltCHHkYBr7+624x6gcijJXVjIcsYk7JXqvIpRtMwU2ARLvWDhmy9ffdFYxhsfJnPztADBohQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3211,15 +3211,15 @@ } }, "node_modules/eslint": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.0.tgz", - "integrity": "sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", + "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.0", + "@eslint/config-array": "^0.23.2", "@eslint/config-helpers": "^0.5.2", "@eslint/core": "^1.1.0", "@eslint/plugin-kit": "^0.6.0", @@ -3227,13 +3227,13 @@ "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.0", - "eslint-visitor-keys": "^5.0.0", - "espree": "^11.1.0", + "eslint-scope": "^9.1.1", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3244,7 +3244,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.1.1", + "minimatch": "^10.2.1", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -7299,14 +7299,14 @@ } }, "node_modules/vue-tsc": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.4.tgz", - "integrity": "sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.5.tgz", + "integrity": "sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA==", "dev": true, "license": "MIT", "dependencies": { - "@volar/typescript": "2.4.27", - "@vue/language-core": "3.2.4" + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.2.5" }, "bin": { "vue-tsc": "bin/vue-tsc.js" diff --git a/webui/package.json b/webui/package.json index f9f03c813..806c44b20 100644 --- a/webui/package.json +++ b/webui/package.json @@ -20,8 +20,8 @@ "@popperjs/core": "^2.11.6", "@ssthouse/vue3-tree-chart": "^0.3.0", "@types/bootstrap": "^5.2.10", - "@types/mokapi": "^0.34.0", - "@types/nodemailer": "^7.0.10", + "@types/mokapi": "^0.35.0", + "@types/nodemailer": "^7.0.11", "@types/whatwg-mimetype": "^5.0.0", "ace-builds": "^1.43.5", "bootstrap": "^5.3.8", @@ -56,14 +56,14 @@ "@vitejs/plugin-vue": "^6.0.4", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.7.0", - "@vue/tsconfig": "^0.8.1", - "eslint": "^10.0.0", + "@vue/tsconfig": "^0.9.0", + "eslint": "^10.0.2", "eslint-plugin-vue": "^10.8.0", "npm-run-all": "^4.1.5", "prettier": "^3.8.1", "typescript": "~5.9.3", "vite": "^7.3.1", - "vue-tsc": "^3.2.4", + "vue-tsc": "^3.2.5", "xml2js": "^0.6.2" } } diff --git a/webui/src/components/dashboard/SchemaValidate.vue b/webui/src/components/dashboard/SchemaValidate.vue index a83881d16..efdf624be 100644 --- a/webui/src/components/dashboard/SchemaValidate.vue +++ b/webui/src/components/dashboard/SchemaValidate.vue @@ -84,19 +84,27 @@ function setExample() { if (example.error) { state.errors = example.error return - }else{ + } else { state.errors = '' } if (state.source.preview) { - state.source.preview.content = example.data[0]!.value + if (example.data[0]?.error) { + state.errors = example.data[0].error + } else { + state.source.preview.content = example.data[0]!.value + } } if (state.source.binary) { let index = 0 if (example.data.length > 1) { index = 1 } - state.source.binary.content = example.data[index]!.value + if (example.data[index]?.error) { + state.errors = example.data[index]!.error! + } else { + state.source.binary.content = example.data[index]!.value + } } example.next() } diff --git a/webui/src/composables/markdown-box.ts b/webui/src/composables/markdown-box.ts index 23e47fd5a..fdd06eadd 100644 --- a/webui/src/composables/markdown-box.ts +++ b/webui/src/composables/markdown-box.ts @@ -101,7 +101,8 @@ export function MarkdownItBox(md: MarkdownIt, opts: Options) { token.hidden = true const url = getUrl(token) - let content = parseContent(token.content) + let content = md.render(token.content) + content = parseContent(content) if (showTitle(token)) { let title = getTitle(token) @@ -127,7 +128,7 @@ export function MarkdownItBox(md: MarkdownIt, opts: Options) { icon = '' break case 'tree': - content = parseTree(content) + content = parseTree(token.content) break } diff --git a/webui/src/composables/markdown-tabs.ts b/webui/src/composables/markdown-tabs.ts index 4d10b29fc..073dffc02 100644 --- a/webui/src/composables/markdown-tabs.ts +++ b/webui/src/composables/markdown-tabs.ts @@ -9,6 +9,7 @@ export function MarkdownItTabs(md: MarkdownIt, opts: Options) { unescapeAll = md.utils.unescapeAll, simple = /tab=([^\s]*)/, quote = /tab="([^"]*)/, + style = /style=([^\s]*)/, counter = 0 function getInfo(token: Token) { @@ -46,6 +47,16 @@ export function MarkdownItTabs(md: MarkdownIt, opts: Options) { return !!getTabName(token) } + function getStyle(token: Token) { + var info = getInfo(token) + + const r = style.exec(info) + if (r && r.length > 1) { + return r.slice(1)[0] + } + return 'default' + } + function fenceGroup(tokens: Token[], idx: number, options: Options, env: any, slf: Renderer): string { const token = tokens[idx] if (!token || token.hidden) return '' @@ -60,32 +71,42 @@ export function MarkdownItTabs(md: MarkdownIt, opts: Options) { const tabId = `tab-${counter}-${tabName}`.replace(' ', '-') const tabPanelId = `tabPanel-${counter}-${tabName}`.replace(' ', '-') - return ` -
- -
-
- ${defaultRender(tokens, idx, options, env, slf)} + const style = getStyle(token) + + if (style === 'default') { + return ` +
+ -
-
` +
+
+ ${defaultRender(tokens, idx, options, env, slf)} +
+
+
` + } + + if (style === 'simple') { + return ` +
${token.content}
+ ` + } } diff --git a/webui/src/views/DocsView.vue b/webui/src/views/DocsView.vue index cc39c78ae..2bb2231d1 100644 --- a/webui/src/views/DocsView.vue +++ b/webui/src/views/DocsView.vue @@ -241,11 +241,13 @@ ol.breadcrumb { .content h1 { margin-top: 0; - font-size: 2.25rem; + font-size: 2.0rem; + font-weight: 900 } .content h2 { - font-size: 1.55rem; + font-size: 1.75rem; + font-weight: 700; } .content h2 > * { @@ -262,7 +264,8 @@ ol.breadcrumb { } .content h3 { - font-size: 1.4rem; + font-size: 1.25rem; + font-weight: 700;; } .content p { @@ -372,17 +375,15 @@ pre { margin-left: 0; } } -code { - background-color: #f1f3f5; +.content code:not(pre code) { color: #e63946; - border-radius: 3px; - padding: 2px 6px; + font-weight: 600 } -.code { +.content .code { margin-bottom: 8px; } -.code pre code.hljs { +.content .code pre code.hljs { font-family: Menlo,Monaco,Consolas,"Courier New",monospace !important; padding-left: 0 !important; } @@ -393,8 +394,7 @@ code { } .box { - padding: 0.6rem; - padding-bottom: 0; + padding: 16px 20px; margin-top: 2rem; margin-bottom: 2rem; border-left-width: 0.2rem ; @@ -408,9 +408,9 @@ code { padding-left: 0.6rem; } .box .box-heading { - margin: -0.6rem -0.6rem 0 -0.6rem; - padding: 0.3rem 0 0.3rem 1rem; - font-weight: 600; + margin: 0 -0.6rem; + padding-left: 1rem; + font-weight: 700; } .box .box-heading:not(.box-custom-heading) { text-transform: capitalize; @@ -425,17 +425,12 @@ code { padding: 0.1em; border-radius: 0.3em; } -.box.info{ - border-color: var(--color-blue); -} -.box.info .box-heading { - background-color: var(--color-blue-shadow); +.box.info, .box.tip{ + border-color: var(--bs-primary-border-subtle); + background-color: var(--bs-primary-bg-subtle); } -.box.tip{ - border-color: var(--color-green); -} -.box.tip .box-heading { - background-color: var(--color-green-shadow); +.box.info .box-heading, .box.tip .box-heading { + color: var(--bs-primary-text-emphasis); } .box.limitation{ border-color: var(--color-orange); @@ -444,16 +439,18 @@ code { background-color: var(--color-orange-shadow); } .box.warning{ - border-color: var(--color-yellow); + border-color: var(--bs-warning-border-subtle); + background-color: var(--bs-warning-bg-subtle); } .box.warning .box-heading { - background-color: var(--color-yellow-shadow); + color: var(--bs-warning-text-emphasis); } .box.result { - border-color: rgb(82, 183, 136); + border-color: var(--bs-success-border-subtle); + background-color: var(--bs-success-bg-subtle); } .box.result .box-heading { - background-color: rgba(82, 183, 136, 0.4); + color: var(--bs-success-text-emphasis); } .anchor { display: block; @@ -602,6 +599,11 @@ a[name] { background-color: var(--card-background); } +.box.benefits .box-heading { + color: var(--color-green); + font-size: 1.1rem; +} + .box.feature { margin-top: 1rem; margin-bottom: 2rem; @@ -674,4 +676,31 @@ a[name] { .emoji { vertical-align: 0.15em; } + +pre.simple { + padding: 24px; + border-radius: 6px; + overflow-x: auto; + margin: 0; + border: 1px solid #21262d; + position: relative; +} +pre.simple code { + padding: 0; +} +pre.simple:has(+ p) { + margin-bottom: 12px; +} +pre::before { + content: attr(data-label); + position: absolute; + top: 8px; + right: 16px; + font-size: 11px; + color: #6e7681; + text-transform: uppercase; + letter-spacing: 0.5px; + font-family: 'JetBrains Mono', monospace; + font-weight: 600; +} \ No newline at end of file diff --git a/webui/src/views/Home.vue b/webui/src/views/Home.vue index d939bbc6b..e75fcdd14 100644 --- a/webui/src/views/Home.vue +++ b/webui/src/views/Home.vue @@ -117,11 +117,28 @@ function showImage(evt: MouseEvent) {
+
+
+
+
+
+

Try Mokapi in a Second

+
+
npx go-mokapi https://petstore31.swagger.io/api/v31/openapi.json
+
+

+ Instantly mock Swagger's Petstore API and start testing +

+
+
+
+
+
+
-
-

Mock and Simulate APIs Across Protocols

@@ -150,7 +166,7 @@ function showImage(evt: MouseEvent) {

Why Teams Use Mokapi

- Mokapi helps teams move faster by removing external dependencies from development and testing. + Stop waiting for backends. Stop fighting flaky external APIs. Start shipping with confidence.

@@ -159,49 +175,35 @@ function showImage(evt: MouseEvent) {

Develop Without Waiting

Mock HTTP APIs, Kafka topics, LDAP directories, or mail servers - so development never blocks on missing or unstable systems. + so development never blocks on missing or unstable backends.

Test Real Workflows

- Simulate realistic system behavior across protocols - and validate integrations with confidence. + Simulate edge cases, error conditions, and complex scenarios + that are hard or impossible to trigger with live systems.

-

Automate Everywhere

+

Ship With Confidence

- Run Mokapi locally, in CI pipelines, or test environments - to automate API testing and speed up feedback loops. + Validate API contracts automatically in CI/CD pipelines, + catching breaking changes before they reach production.

-
-
-

Build Better Software, Faster

-

- Mokapi helps teams move quickly without sacrificing confidence or stability. -

-

- By mocking and simulating APIs across protocols, you can automate tests, - reduce flaky integrations, and deliver reliable software — even when - external systems are unavailable or evolving. -

-
-
-

Mock More Than Just HTTP

- Mokapi supports multiple protocols, allowing you to test complete systems — + Mokapi supports multiple protocols, allowing you to test complete systems, not just individual REST endpoints.

@@ -288,22 +290,6 @@ function showImage(evt: MouseEvent) {
-
-
-

Built for Reliable Development and Testing

-

- Mocking APIs across protocols is only the beginning. - Mokapi is designed to help teams prevent bugs, reduce external dependencies, - and create stable development and test environments. -

-

- This is made possible through powerful core features — - including JavaScript-based logic, configuration patching, - observability, and realistic data generation. -

-
-
-
@@ -326,7 +312,7 @@ function showImage(evt: MouseEvent) { without changing your API specifications.

- Explore JavaScript Mocking + Explore JavaScript API
@@ -334,62 +320,61 @@ function showImage(evt: MouseEvent) {
- +
-

Run Mocks Anywhere

+

Generate Realistic Test Data

- Run Mokapi in any environment—local development, Docker, cloud, or CI pipelines. Test APIs seamlessly, wherever your services are deployed. + Create dynamic, lifelike data for your mocks. Simulate users, transactions, messages, and more to improve testing accuracy.

- Ensure consistent testing across local development, CI pipelines, and cloud environments. + Produce realistic data to catch bugs early and test edge cases that rarely occur in production.

- - Learn How to Run Mokapi + + Learn about Fake Data
- Log output starting Mokapi in a Docker image. + Mokapi Faker decision tree for generating realistic random data.
- - +
-

Define Mocks as Code

+

Run Mocks Anywhere

- Manage all API mocks, configurations, and behaviors as code. Track changes, simplify audits, and ensure consistency across environments. + Run Mokapi in any environment: local development, Docker, cloud, or CI pipelines. Test APIs seamlessly, wherever your services are deployed.

- Version-controlled mocks reduce errors, simplify audits, and make collaboration easier. + Ensure consistent testing across local development, CI pipelines, and cloud environments.

- - Learn More + + Deployment Options
- OpenAPI spec and scripts to generate mock data. + Log output starting Mokapi in a Docker image.
- +
-

Generate Realistic Test Data

+

Define Mocks as Code

- Create dynamic, lifelike data for your mocks. Simulate users, transactions, messages, and more to improve testing accuracy. + Manage all API mocks, configurations, and behaviors as code. Track changes, simplify audits, and ensure consistency across environments.

- Produce lifelike data to catch bugs early and test edge cases that rarely occur in production. + Version-controlled mocks reduce errors, simplify audits, and make collaboration easier.

- - Explore Fake Data Features + + Configuration Guide
- Mokapi Faker decision tree for generating realistic random data. + OpenAPI spec and scripts to generate mock data.
@@ -471,7 +456,7 @@ function showImage(evt: MouseEvent) {

Simulate Kafka Topics with AsyncAPI

-

Test Kafka producers and consumers by mocking topics according to your AsyncAPI spec. Ensure reliable message generation and integration without a live Kafka cluster.

+

Test Kafka producers and consumers by mocking topics according to your AsyncAPI spec. Ensure reliable message generation without a live Kafka cluster.

Start Tutorial @@ -484,7 +469,7 @@ function showImage(evt: MouseEvent) {

Mock LDAP Authentication

-

Step-by-step guide to mock LDAP login using Mokapi and Node.js. Test authentication flows without a real server.

+

Step-by-step guide to mock LDAP login using Mokapi and Node.js. Test authentication flows without a real directory server.

Start Tutorial @@ -503,18 +488,28 @@ function showImage(evt: MouseEvent) { - - - -
+

Enforce API Contracts

-

Validate HTTP requests and responses against OpenAPI specs to catch API issues early in development or testing.

- Read Blog +

Validate HTTP requests and responses against OpenAPI specs to catch breaking changes early in development or testing.

+ Read Article +
+
+
+ + +
+
+
+

+ Record & Replay API Traffic +

+

Capture real API interactions and replay them in tests. Build test suites from actual production behavior.

+ Read Article
@@ -623,6 +618,26 @@ function showImage(evt: MouseEvent) { } } +.quick-start-code { + background: #1a1a1a; + color: #00ff00; + padding: 1rem 1.5rem; + border-radius: 6px; + margin: 0; + font-family: 'JetBrains Mono', 'Courier New', monospace; + font-size: 0.9rem; + max-width: 100%; +} + +.quick-start-code code { + word-break: break-word; +} + +.quick-start-desc { + font-size: 0.9rem; + margin-top: 0.75rem; +} + ul.nav-vertical { padding: 0; margin: 0;