├── .gitignore ├── docs └── images │ └── logo │ ├── logo.png │ └── opengraph.png ├── testdata ├── expected-template-basic.json ├── expected-template-transformerParameters.json ├── must-equal-to-json.json ├── url-path-templating.json ├── not-logical-expression.json ├── matches-Json-schema.json ├── expected-template-bearer-auth-contains.json ├── expected-template-bearer-auth-equalTo.json ├── expected-template-bearer-auth-matching.json ├── expected-template-bearer-auth-startsWith.json ├── expected-template-bearer-auth-logicalMatcher.json └── expected-template-scenario.json ├── grpc ├── go.mod ├── testdata │ ├── error.json │ ├── greeting_service.proto │ ├── ok.json │ └── greeting_service.pb.go ├── grpc_service.go ├── go.sum ├── stub_rule_builder.go ├── stub_rule_builder_test.go └── response_builder.go ├── .github └── workflows │ └── build.yml ├── LICENSE ├── multi_value_matcher.go ├── matching.go ├── delay.go ├── logical_matcher.go ├── url_matcher.go ├── doc.go ├── multipart_pattern.go ├── string_value_matcher_test.go ├── go.mod ├── webhook.go ├── journal └── model.go ├── request.go ├── response.go ├── string_value_matcher.go ├── stub_rule_test.go ├── README.md ├── stub_rule.go ├── client_test.go ├── client.go └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /docs/images/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiremock/go-wiremock/HEAD/docs/images/logo/logo.png -------------------------------------------------------------------------------- /docs/images/logo/opengraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiremock/go-wiremock/HEAD/docs/images/logo/opengraph.png -------------------------------------------------------------------------------- /testdata/expected-template-basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request": { 5 | "method": "PATCH", 6 | "urlPattern": "/example" 7 | }, 8 | "response": { 9 | "status": 200 10 | } 11 | } -------------------------------------------------------------------------------- /grpc/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wiremock/go-wiremock/grpc 2 | 3 | go 1.23.6 4 | 5 | require ( 6 | github.com/wiremock/go-wiremock v1.11.0 7 | google.golang.org/grpc v1.73.0 8 | google.golang.org/protobuf v1.36.6 9 | ) 10 | 11 | require ( 12 | github.com/google/uuid v1.6.0 // indirect 13 | golang.org/x/sys v0.33.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /grpc/testdata/error.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request" : { 5 | "urlPath" : "/com.example.grpc.GreetingService/greeting", 6 | "method" : "POST" 7 | }, 8 | "response" : { 9 | "status" : 200, 10 | "headers" : { 11 | "grpc-status-name" : "UNAVAILABLE", 12 | "grpc-status-reason": "Unavailable" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /testdata/expected-template-transformerParameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request" : { 5 | "method": "GET", 6 | "urlPathTemplate" : "/templated" 7 | }, 8 | "response" : { 9 | "status" : 200, 10 | "transformers": ["response-template"], 11 | "transformerParameters": { 12 | "MyCustomParameter": "Parameter Value" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /testdata/must-equal-to-json.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request": { 5 | "method": "PATCH", 6 | "urlPattern": "/example", 7 | "bodyPatterns": [ 8 | { 9 | "equalToJson": "{\"meta\":\"information\"}", 10 | "ignoreArrayOrder" : true, 11 | "ignoreExtraElements" : true 12 | } 13 | ] 14 | }, 15 | "response": { 16 | "status": 200 17 | } 18 | } -------------------------------------------------------------------------------- /grpc/testdata/greeting_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package com.example.greeting.v1; 4 | 5 | option java_multiple_files = true; 6 | 7 | option go_package = "grpc/testdata"; 8 | 9 | message GreetingRequest { 10 | string name = 1; 11 | } 12 | 13 | message GreetingResponse { 14 | string greeting = 1; 15 | } 16 | 17 | service GreetingService { 18 | rpc greet(GreetingRequest) returns (GreetingResponse); 19 | } -------------------------------------------------------------------------------- /testdata/url-path-templating.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request" : { 5 | "urlPathTemplate" : "/contacts/{contactId}/addresses/{addressId}", 6 | "method" : "GET", 7 | "pathParameters" : { 8 | "contactId" : { 9 | "equalTo" : "12345" 10 | }, 11 | "addressId" : { 12 | "equalTo" : "99876" 13 | } 14 | } 15 | }, 16 | "response" : { 17 | "status" : 200 18 | } 19 | } -------------------------------------------------------------------------------- /grpc/testdata/ok.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request" : { 5 | "urlPath" : "/com.example.grpc.GreetingService/greeting", 6 | "method" : "POST", 7 | "bodyPatterns": [ 8 | { 9 | "equalToJson": "{\"name\":\"Tom\"}" 10 | } 11 | ] 12 | }, 13 | "response" : { 14 | "status" : 200, 15 | "body": "{\"greeting\":\"Hello Tom\"}", 16 | "headers" : { 17 | "grpc-status-name" : "OK" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /testdata/not-logical-expression.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request": { 5 | "method": "POST", 6 | "urlPath": "/example", 7 | "queryParameters": { 8 | "firstName": { 9 | "not": { 10 | "or": [ 11 | { 12 | "equalTo": "John" 13 | }, 14 | { 15 | "equalTo": "Jack" 16 | } 17 | ] 18 | } 19 | } 20 | } 21 | }, 22 | "response": { 23 | "status": 200 24 | } 25 | } -------------------------------------------------------------------------------- /testdata/matches-Json-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request": { 5 | "method": "POST", 6 | "urlPath": "/example", 7 | "queryParameters": { 8 | "firstName": { 9 | "matchesJsonSchema" : "{\n \"type\": \"object\",\n \"required\": [\n \"name\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"tag\": {\n \"type\": \"string\"\n }\n }\n}", 10 | "schemaVersion" : "V202012" 11 | } 12 | } 13 | }, 14 | "response": { 15 | "status": 200 16 | } 17 | } -------------------------------------------------------------------------------- /testdata/expected-template-bearer-auth-contains.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request": { 5 | "headers": { 6 | "Authorization": { 7 | "and": [ 8 | { 9 | "matches": "^Bearer \\s*\\S*" 10 | }, 11 | { 12 | "contains": "token" 13 | } 14 | ] 15 | } 16 | }, 17 | "host": { 18 | "equalTo": "localhost" 19 | }, 20 | "method": "POST", 21 | "port": 8080, 22 | "scheme": "http", 23 | "urlPath": "/example" 24 | }, 25 | "response": { 26 | "status": 200 27 | } 28 | } -------------------------------------------------------------------------------- /testdata/expected-template-bearer-auth-equalTo.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request": { 5 | "headers": { 6 | "Authorization": { 7 | "and": [ 8 | { 9 | "matches": "^Bearer \\s*\\S*" 10 | }, 11 | { 12 | "equalTo": "Bearer token" 13 | } 14 | ] 15 | } 16 | }, 17 | "host": { 18 | "equalTo": "localhost" 19 | }, 20 | "method": "POST", 21 | "port": 8080, 22 | "scheme": "http", 23 | "urlPath": "/example" 24 | }, 25 | "response": { 26 | "status": 200 27 | } 28 | } -------------------------------------------------------------------------------- /testdata/expected-template-bearer-auth-matching.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request": { 5 | "headers": { 6 | "Authorization": { 7 | "and": [ 8 | { 9 | "matches": "^Bearer \\s*\\S*" 10 | }, 11 | { 12 | "matches": "^Bearer t?o?ken$" 13 | } 14 | ] 15 | } 16 | }, 17 | "host": { 18 | "equalTo": "localhost" 19 | }, 20 | "method": "POST", 21 | "port": 8080, 22 | "scheme": "http", 23 | "urlPath": "/example" 24 | }, 25 | "response": { 26 | "status": 200 27 | } 28 | } -------------------------------------------------------------------------------- /testdata/expected-template-bearer-auth-startsWith.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request": { 5 | "headers": { 6 | "Authorization": { 7 | "and": [ 8 | { 9 | "matches": "^Bearer \\s*\\S*" 10 | }, 11 | { 12 | "matches": "^Bearer token\\s*\\S*" 13 | } 14 | ] 15 | } 16 | }, 17 | "host": { 18 | "equalTo": "localhost" 19 | }, 20 | "method": "POST", 21 | "port": 8080, 22 | "scheme": "http", 23 | "urlPath": "/example" 24 | }, 25 | "response": { 26 | "status": 200 27 | } 28 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | lint: 9 | name: lint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v3 13 | with: 14 | go-version: '1.23' 15 | - uses: actions/checkout@v3 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@v3 18 | with: 19 | version: latest 20 | tests: 21 | name: tests 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/setup-go@v3 25 | with: 26 | go-version: '1.23' 27 | - uses: actions/checkout@v3 28 | - name: Tests 29 | run: go test ./... -------------------------------------------------------------------------------- /grpc/grpc_service.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "github.com/wiremock/go-wiremock" 5 | ) 6 | 7 | type stubRuleBuilder interface { 8 | Build(serviceName string) *wiremock.StubRule 9 | } 10 | 11 | type Service struct { 12 | serviceName string 13 | wiremock *wiremock.Client 14 | } 15 | 16 | // NewService creates a new instance of the Service 17 | func NewService(serviceName string, wiremock *wiremock.Client) *Service { 18 | return &Service{ 19 | serviceName: serviceName, 20 | wiremock: wiremock, 21 | } 22 | } 23 | 24 | // StubFor creates a new stub mapping for grpc service. 25 | func (s *Service) StubFor(builder stubRuleBuilder) error { 26 | return s.wiremock.StubFor(builder.Build(s.serviceName)) 27 | } 28 | -------------------------------------------------------------------------------- /testdata/expected-template-bearer-auth-logicalMatcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "request": { 5 | "headers": { 6 | "Authorization": { 7 | "and": [ 8 | { 9 | "matches": "^Bearer \\s*\\S*" 10 | }, 11 | { 12 | "and": [ 13 | { 14 | "equalTo": "Bearer token123" 15 | }, 16 | { 17 | "matches": "^Bearer token\\s*\\S*" 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | "host": { 25 | "equalTo": "localhost" 26 | }, 27 | "method": "POST", 28 | "port": 8080, 29 | "scheme": "http", 30 | "urlPath": "/example" 31 | }, 32 | "response": { 33 | "status": 200 34 | } 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2023 Ruslan Isamukhametov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /multi_value_matcher.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import "encoding/json" 4 | 5 | type MultiValueMatcher struct { 6 | strategy MultiValueMatchingStrategy 7 | matchers []BasicParamMatcher 8 | } 9 | 10 | // MarshalJSON returns the JSON encoding of the matcher. 11 | func (m MultiValueMatcher) MarshalJSON() ([]byte, error) { 12 | return json.Marshal(m.ParseMatcher()) 13 | } 14 | 15 | // ParseMatcher returns the map representation of the structure. 16 | func (m MultiValueMatcher) ParseMatcher() map[string]interface{} { 17 | return map[string]interface{}{ 18 | string(m.strategy): m.matchers, 19 | } 20 | } 21 | 22 | // HasExactly returns a matcher that matches when the parameter has exactly the specified values. 23 | func HasExactly(matchers ...BasicParamMatcher) MultiValueMatcher { 24 | return MultiValueMatcher{ 25 | strategy: ParamHasExactly, 26 | matchers: matchers, 27 | } 28 | } 29 | 30 | // Includes returns a matcher that matches when the parameter includes the specified values. 31 | func Includes(matchers ...BasicParamMatcher) MultiValueMatcher { 32 | return MultiValueMatcher{ 33 | strategy: ParamIncludes, 34 | matchers: matchers, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /grpc/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 2 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 3 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 4 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | github.com/wiremock/go-wiremock v1.11.0 h1:BL4uVNgUV3uHbavAro4c0EIa/IIOeV7icO34Jg87ZjQ= 6 | github.com/wiremock/go-wiremock v1.11.0/go.mod h1:/uvO0XFheyy8XetvQqm4TbNQRsGPlByeNegzLzvXs0c= 7 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 8 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 9 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 10 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 11 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= 12 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 13 | google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= 14 | google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= 15 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 16 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 17 | -------------------------------------------------------------------------------- /grpc/stub_rule_builder.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "google.golang.org/protobuf/encoding/protojson" 7 | "google.golang.org/protobuf/proto" 8 | 9 | "github.com/wiremock/go-wiremock" 10 | ) 11 | 12 | type StubRuleBuilder struct { 13 | method string 14 | responseBuilder *ResponseBuilder 15 | bodyPatterns []wiremock.BasicParamMatcher 16 | } 17 | 18 | // Method creates a new instance of the StubRuleBuilder with grpc method. 19 | func Method(method string) *StubRuleBuilder { 20 | return &StubRuleBuilder{method: method} 21 | } 22 | 23 | // EqualToMessage grpc alias for wiremock.MustEqualToJson. May panic if there are problems with marshaling. 24 | func EqualToMessage(message proto.Message) wiremock.BasicParamMatcher { 25 | data, err := protojson.Marshal(message) 26 | 27 | if err != nil { 28 | panic(fmt.Sprintf("failed to marshal proto message: %v", err)) 29 | } 30 | 31 | return wiremock.EqualToJson(string(data)) 32 | } 33 | 34 | // WithRequestMessage adds a request message matcher to the stub rule. 35 | func (s *StubRuleBuilder) WithRequestMessage(matcher wiremock.BasicParamMatcher) *StubRuleBuilder { 36 | s.bodyPatterns = append(s.bodyPatterns, matcher) 37 | return s 38 | } 39 | 40 | // WillReturn sets the response for the stub rule. 41 | func (s *StubRuleBuilder) WillReturn(responseBuilder *ResponseBuilder) *StubRuleBuilder { 42 | s.responseBuilder = responseBuilder 43 | return s 44 | } 45 | 46 | // Build builds a new instance of the StubRule. 47 | func (s *StubRuleBuilder) Build(serviceName string) *wiremock.StubRule { 48 | stubRule := wiremock.Post(wiremock.URLPathEqualTo(fmt.Sprintf("/%s/%s", serviceName, s.method))) 49 | for _, bodyPattern := range s.bodyPatterns { 50 | stubRule = stubRule.WithBodyPattern(bodyPattern) 51 | } 52 | 53 | return stubRule.WillReturnResponse(s.responseBuilder.Build()) 54 | } 55 | -------------------------------------------------------------------------------- /matching.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | // Types of params matching. 4 | const ( 5 | ParamEqualTo ParamMatchingStrategy = "equalTo" 6 | ParamMatches ParamMatchingStrategy = "matches" 7 | ParamContains ParamMatchingStrategy = "contains" 8 | ParamEqualToXml ParamMatchingStrategy = "equalToXml" 9 | ParamEqualToJson ParamMatchingStrategy = "equalToJson" 10 | ParamMatchesXPath ParamMatchingStrategy = "matchesXPath" 11 | ParamMatchesJsonPath ParamMatchingStrategy = "matchesJsonPath" 12 | ParamAbsent ParamMatchingStrategy = "absent" 13 | ParamDoesNotMatch ParamMatchingStrategy = "doesNotMatch" 14 | ParamDoesNotContains ParamMatchingStrategy = "doesNotContain" 15 | ParamMatchesJsonSchema ParamMatchingStrategy = "matchesJsonSchema" 16 | ) 17 | 18 | // Types of url matching. 19 | const ( 20 | URLEqualToRule URLMatchingStrategy = "url" 21 | URLPathEqualToRule URLMatchingStrategy = "urlPath" 22 | URLPathMatchingRule URLMatchingStrategy = "urlPathPattern" 23 | URLMatchingRule URLMatchingStrategy = "urlPattern" 24 | URLPathTemplateRule URLMatchingStrategy = "urlPathTemplate" 25 | ) 26 | 27 | // Type of less strict matching flags. 28 | const ( 29 | IgnoreArrayOrder EqualFlag = "ignoreArrayOrder" 30 | IgnoreExtraElements EqualFlag = "ignoreExtraElements" 31 | ) 32 | 33 | const ( 34 | ParamHasExactly MultiValueMatchingStrategy = "hasExactly" 35 | ParamIncludes MultiValueMatchingStrategy = "includes" 36 | ) 37 | 38 | // EqualFlag is enum of less strict matching flag. 39 | type EqualFlag string 40 | 41 | // URLMatchingStrategy is enum url matching type. 42 | type URLMatchingStrategy string 43 | 44 | // ParamMatchingStrategy is enum params matching type. 45 | type ParamMatchingStrategy string 46 | 47 | // MultiValueMatchingStrategy is enum multi value matching type. 48 | type MultiValueMatchingStrategy string 49 | -------------------------------------------------------------------------------- /delay.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | type DelayInterface interface { 9 | ParseDelay() map[string]interface{} 10 | } 11 | 12 | type fixedDelay struct { 13 | milliseconds int64 14 | } 15 | 16 | func (d fixedDelay) ParseDelay() map[string]interface{} { 17 | return map[string]interface{}{ 18 | "type": "fixed", 19 | "milliseconds": d.milliseconds, 20 | } 21 | } 22 | 23 | type logNormalRandomDelay struct { 24 | median int64 25 | sigma float64 26 | } 27 | 28 | func (d logNormalRandomDelay) ParseDelay() map[string]interface{} { 29 | return map[string]interface{}{ 30 | "type": "lognormal", 31 | "median": d.median, 32 | "sigma": d.sigma, 33 | } 34 | } 35 | 36 | type uniformRandomDelay struct { 37 | lower int64 38 | upper int64 39 | } 40 | 41 | func (d uniformRandomDelay) ParseDelay() map[string]interface{} { 42 | return map[string]interface{}{ 43 | "type": "uniform", 44 | "lower": d.lower, 45 | "upper": d.upper, 46 | } 47 | } 48 | 49 | type chunkedDribbleDelay struct { 50 | numberOfChunks int64 51 | totalDuration int64 52 | } 53 | 54 | func (d chunkedDribbleDelay) MarshalJSON() ([]byte, error) { 55 | jsonMap := map[string]interface{}{ 56 | "numberOfChunks": d.numberOfChunks, 57 | "totalDuration": d.totalDuration, 58 | } 59 | 60 | return json.Marshal(jsonMap) 61 | } 62 | 63 | func NewLogNormalRandomDelay(median time.Duration, sigma float64) DelayInterface { 64 | return logNormalRandomDelay{ 65 | median: median.Milliseconds(), 66 | sigma: sigma, 67 | } 68 | } 69 | 70 | func NewFixedDelay(delay time.Duration) DelayInterface { 71 | return fixedDelay{ 72 | milliseconds: delay.Milliseconds(), 73 | } 74 | } 75 | 76 | func NewUniformRandomDelay(lower, upper time.Duration) DelayInterface { 77 | return uniformRandomDelay{ 78 | lower: lower.Milliseconds(), 79 | upper: upper.Milliseconds(), 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /logical_matcher.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type LogicalMatcher struct { 8 | operator string 9 | operands []BasicParamMatcher 10 | } 11 | 12 | // MarshalJSON returns the JSON encoding of the matcher. 13 | func (m LogicalMatcher) MarshalJSON() ([]byte, error) { 14 | return json.Marshal(m.ParseMatcher()) 15 | } 16 | 17 | // ParseMatcher returns the map representation of the structure. 18 | func (m LogicalMatcher) ParseMatcher() map[string]interface{} { 19 | if m.operator == "not" { 20 | return map[string]interface{}{ 21 | m.operator: m.operands[0], 22 | } 23 | } 24 | 25 | return map[string]interface{}{ 26 | m.operator: m.operands, 27 | } 28 | } 29 | 30 | // Or returns a logical OR of the current matcher and the given matcher. 31 | func (m LogicalMatcher) Or(matcher BasicParamMatcher) BasicParamMatcher { 32 | if m.operator == "or" { 33 | m.operands = append(m.operands, matcher) 34 | return m 35 | } 36 | 37 | return Or(m, matcher) 38 | } 39 | 40 | // And returns a logical AND of the current matcher and the given matcher. 41 | func (m LogicalMatcher) And(matcher BasicParamMatcher) BasicParamMatcher { 42 | if m.operator == "and" { 43 | m.operands = append(m.operands, matcher) 44 | return m 45 | } 46 | 47 | return And(m, matcher) 48 | } 49 | 50 | // Or returns a logical OR of the list of matchers. 51 | func Or(matchers ...BasicParamMatcher) LogicalMatcher { 52 | return LogicalMatcher{ 53 | operator: "or", 54 | operands: matchers, 55 | } 56 | } 57 | 58 | // And returns a logical AND of the list of matchers. 59 | func And(matchers ...BasicParamMatcher) LogicalMatcher { 60 | return LogicalMatcher{ 61 | operator: "and", 62 | operands: matchers, 63 | } 64 | } 65 | 66 | // Not returns a logical NOT of the given matcher. Required wiremock version >= 3.0.0 67 | func Not(matcher BasicParamMatcher) LogicalMatcher { 68 | return LogicalMatcher{ 69 | operator: "not", 70 | operands: []BasicParamMatcher{matcher}, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /url_matcher.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | // URLMatcherInterface is pair URLMatchingStrategy and string matched value 4 | type URLMatcherInterface interface { 5 | Strategy() URLMatchingStrategy 6 | Value() string 7 | } 8 | 9 | // URLMatcher is structure for defining the type of url matching. 10 | type URLMatcher struct { 11 | strategy URLMatchingStrategy 12 | value string 13 | } 14 | 15 | // Strategy returns URLMatchingStrategy of URLMatcher. 16 | func (m URLMatcher) Strategy() URLMatchingStrategy { 17 | return m.strategy 18 | } 19 | 20 | // Value returns value of URLMatcher. 21 | func (m URLMatcher) Value() string { 22 | return m.value 23 | } 24 | 25 | // URLEqualTo returns URLMatcher with URLEqualToRule matching strategy. 26 | func URLEqualTo(url string) URLMatcher { 27 | return URLMatcher{ 28 | strategy: URLEqualToRule, 29 | value: url, 30 | } 31 | } 32 | 33 | // URLPathEqualTo returns URLMatcher with URLPathEqualToRule matching strategy. 34 | func URLPathEqualTo(url string) URLMatcher { 35 | return URLMatcher{ 36 | strategy: URLPathEqualToRule, 37 | value: url, 38 | } 39 | } 40 | 41 | // URLPathMatching returns URLMatcher with URLPathMatchingRule matching strategy. 42 | func URLPathMatching(url string) URLMatcher { 43 | return URLMatcher{ 44 | strategy: URLPathMatchingRule, 45 | value: url, 46 | } 47 | } 48 | 49 | // URLMatching returns URLMatcher with URLMatchingRule matching strategy. 50 | func URLMatching(url string) URLMatcher { 51 | return URLMatcher{ 52 | strategy: URLMatchingRule, 53 | value: url, 54 | } 55 | } 56 | 57 | // URLPathTemplate URL paths can be matched using URI templates, conforming to the same subset of the URI template standard as used in OpenAPI. 58 | // Path variable matchers can also be used in the same manner as query and form parameters. 59 | // Required wiremock >= 3.0.0 60 | // Example: /contacts/{contactId}/addresses/{addressId} 61 | func URLPathTemplate(url string) URLMatcher { 62 | return URLMatcher{ 63 | strategy: URLPathTemplateRule, 64 | value: url, 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 go-wiremock maintainers 2 | // license that can be found in the LICENSE file. 3 | 4 | /* 5 | Package wiremock is client for WireMock API. 6 | WireMock is a simulator for HTTP-based APIs. 7 | Some might consider it a service virtualization tool or a mock server. 8 | 9 | HTTP request: 10 | 11 | POST /example?firstName=John&lastName=Any string other than "Gray" HTTP/1.1 12 | Host: 0.0.0.0:8080 13 | x-session: somefingerprintsome 14 | Content-Type: application/json 15 | Content-Length: 23 16 | 17 | {"meta": "information"} 18 | 19 | With response: 20 | 21 | Status: 400 Bad Request 22 | "Content-Type": "application/json" 23 | {"code": 400, "detail": "detail"} 24 | 25 | Stub: 26 | 27 | client := wiremock.NewClient("http://0.0.0.0:8080") 28 | client.StubFor(wiremock.Post(wiremock.URLPathEqualTo("/example")). 29 | WithQueryParam("firstName", wiremock.EqualTo("John")). 30 | WithQueryParam("lastName", wiremock.NotMatching("Gray")). 31 | WithBodyPattern(wiremock.EqualToJson(`{"meta": "information"}`)). 32 | WithHeader("x-session", wiremock.Matching("^\\S+fingerprint\\S+$")). 33 | WillReturnResponse( 34 | wiremock.NewResponse(). 35 | WithStatus(http.StatusBadRequest). 36 | WithHeader("Content-Type", "application/json"). 37 | WithBody(`{"code": 400, "detail": "detail"}`), 38 | ). 39 | AtPriority(1)) 40 | 41 | The client should reset all made stubs after tests: 42 | 43 | client := wiremock.NewClient("http://0.0.0.0:8080") 44 | defer wiremock.Reset() 45 | client.StubFor(wiremock.Get(wiremock.URLPathEqualTo("/example"))) 46 | client.StubFor(wiremock.Get(wiremock.URLPathEqualTo("/example/1"))) 47 | // ... 48 | 49 | To avoid conflicts, you can delete individual rules: 50 | 51 | client := wiremock.NewClient("http://0.0.0.0:8080") 52 | exampleStubRule := wiremock.Get(wiremock.URLPathEqualTo("/example")) 53 | client.StubFor(exampleStubRule) 54 | client.StubFor(wiremock.Get(wiremock.URLPathEqualTo("/example/1"))) 55 | // ... 56 | client.DeleteStub(exampleStubRule) 57 | 58 | You can verify if a request has been made that matches the mapping. 59 | 60 | client := wiremock.NewClient("http://0.0.0.0:8080") 61 | exampleStubRule := wiremock.Get(wiremock.URLPathEqualTo("/example")) 62 | client.StubFor(exampleStubRule) 63 | // ... 64 | client.Verify(exampleStubRule.Request(), 1) 65 | */ 66 | package wiremock 67 | -------------------------------------------------------------------------------- /multipart_pattern.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | const ( 9 | MultipartMatchingTypeAny = "ANY" 10 | MultipartMatchingTypeAll = "ALL" 11 | ) 12 | 13 | type MultipartMatchingType string 14 | 15 | type MultipartPatternInterface interface { 16 | json.Marshaler 17 | ParseMultipartPattern() map[string]interface{} 18 | } 19 | 20 | type MultipartPattern struct { 21 | matchingType MultipartMatchingType 22 | headers map[string]MatcherInterface 23 | bodyPatterns []BasicParamMatcher 24 | } 25 | 26 | func NewMultipartPattern() *MultipartPattern { 27 | return &MultipartPattern{ 28 | matchingType: MultipartMatchingTypeAny, 29 | } 30 | } 31 | 32 | func (m *MultipartPattern) WithName(name string) *MultipartPattern { 33 | if m.headers == nil { 34 | m.headers = map[string]MatcherInterface{} 35 | } 36 | 37 | m.headers["Content-Disposition"] = Contains(fmt.Sprintf(`name="%s"`, name)) 38 | return m 39 | } 40 | 41 | func (m *MultipartPattern) WithMatchingType(matchingType MultipartMatchingType) *MultipartPattern { 42 | m.matchingType = matchingType 43 | return m 44 | } 45 | 46 | func (m *MultipartPattern) WithAllMatchingType() *MultipartPattern { 47 | m.matchingType = MultipartMatchingTypeAll 48 | return m 49 | } 50 | 51 | func (m *MultipartPattern) WithAnyMatchingType() *MultipartPattern { 52 | m.matchingType = MultipartMatchingTypeAny 53 | return m 54 | } 55 | 56 | func (m *MultipartPattern) WithBodyPattern(matcher BasicParamMatcher) *MultipartPattern { 57 | m.bodyPatterns = append(m.bodyPatterns, matcher) 58 | return m 59 | } 60 | 61 | func (m *MultipartPattern) WithHeader(header string, matcher MatcherInterface) *MultipartPattern { 62 | if m.headers == nil { 63 | m.headers = map[string]MatcherInterface{} 64 | } 65 | 66 | m.headers[header] = matcher 67 | return m 68 | } 69 | 70 | // MarshalJSON gives valid JSON or error. 71 | func (m *MultipartPattern) MarshalJSON() ([]byte, error) { 72 | return json.Marshal(m.ParseMultipartPattern()) 73 | } 74 | 75 | func (m *MultipartPattern) ParseMultipartPattern() map[string]interface{} { 76 | multipart := map[string]interface{}{ 77 | "matchingType": m.matchingType, 78 | } 79 | 80 | if len(m.bodyPatterns) > 0 { 81 | multipart["bodyPatterns"] = m.bodyPatterns 82 | } 83 | 84 | if len(m.headers) > 0 { 85 | multipart["headers"] = m.headers 86 | } 87 | 88 | return multipart 89 | } 90 | -------------------------------------------------------------------------------- /grpc/stub_rule_builder_test.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "reflect" 9 | "testing" 10 | 11 | "google.golang.org/grpc/codes" 12 | 13 | "github.com/wiremock/go-wiremock" 14 | "github.com/wiremock/go-wiremock/grpc/testdata" 15 | ) 16 | 17 | const testDataDir = "testdata" 18 | 19 | func TestStubRule_ToJson(t *testing.T) { 20 | testCases := []struct { 21 | Name string 22 | StubRule *wiremock.StubRule 23 | ExpectedFileName string 24 | }{ 25 | { 26 | Name: "Error", 27 | StubRule: Method("greeting"). 28 | WillReturn(Error(codes.Unavailable, "Unavailable")). 29 | Build("com.example.grpc.GreetingService"), 30 | ExpectedFileName: "error.json", 31 | }, 32 | { 33 | Name: "OK Message", 34 | StubRule: Method("greeting"). 35 | WithRequestMessage(EqualToMessage(&testdata.GreetingRequest{Name: "Tom"})). 36 | WillReturn(Message(&testdata.GreetingResponse{Greeting: "Hello Tom"})). 37 | Build("com.example.grpc.GreetingService"), 38 | ExpectedFileName: "ok.json", 39 | }, 40 | { 41 | Name: "OK JSON", 42 | StubRule: Method("greeting"). 43 | WithRequestMessage(wiremock.EqualToJson(`{"name":"Tom"}`)). 44 | WillReturn(JSON(`{"greeting":"Hello Tom"}`)). 45 | Build("com.example.grpc.GreetingService"), 46 | ExpectedFileName: "ok.json", 47 | }, 48 | } 49 | 50 | for _, tc := range testCases { 51 | t.Run(tc.Name, func(t *testing.T) { 52 | stubRule := tc.StubRule 53 | 54 | rawExpectedRequestBody, err := os.ReadFile(filepath.Join(testDataDir, tc.ExpectedFileName)) 55 | if err != nil { 56 | t.Fatalf("failed to read expected JSON file %s: %v", tc.ExpectedFileName, err) 57 | } 58 | 59 | var expected map[string]interface{} 60 | err = json.Unmarshal([]byte(fmt.Sprintf(string(rawExpectedRequestBody), stubRule.UUID(), stubRule.UUID())), &expected) 61 | if err != nil { 62 | t.Fatalf("StubRule json.Unmarshal error: %v", err) 63 | } 64 | 65 | rawResult, err := json.Marshal(stubRule) 66 | if err != nil { 67 | t.Fatalf("StubRule json.Marshal error: %v", err) 68 | } 69 | 70 | var parsedResult map[string]interface{} 71 | err = json.Unmarshal(rawResult, &parsedResult) 72 | if err != nil { 73 | t.Fatalf("StubRule json.Unmarshal error: %v", err) 74 | } 75 | 76 | if !reflect.DeepEqual(parsedResult, expected) { 77 | t.Errorf("expected JSON:\n%v\nactual JSON:\n%v", parsedResult, expected) 78 | } 79 | }) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /grpc/response_builder.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/protobuf/encoding/protojson" 9 | "google.golang.org/protobuf/proto" 10 | 11 | "github.com/wiremock/go-wiremock" 12 | ) 13 | 14 | const ( 15 | responseStatusName = "grpc-status-name" 16 | responseStatusReason = "grpc-status-reason" 17 | ) 18 | 19 | type ResponseBuilder struct { 20 | grpcResponseStatus codes.Code 21 | grpcStatusReason *string 22 | fault *wiremock.Fault 23 | body string 24 | delay wiremock.DelayInterface 25 | } 26 | 27 | // Error creates a response builder with a gRPC error status and reason. 28 | func Error(grpcResponseStatus codes.Code, grpcResponseReason string) *ResponseBuilder { 29 | return &ResponseBuilder{ 30 | grpcResponseStatus: grpcResponseStatus, 31 | grpcStatusReason: &grpcResponseReason, 32 | } 33 | } 34 | 35 | // JSON creates a response builder with a JSON body. 36 | func JSON(json string) *ResponseBuilder { 37 | return &ResponseBuilder{ 38 | body: json, 39 | } 40 | } 41 | 42 | // Message creates a response builder with a serialized proto message as the body. Can panic if there are problems with marshaling. 43 | func Message(message proto.Message) *ResponseBuilder { 44 | bytes, err := protojson.Marshal(message) 45 | if err != nil { 46 | panic("failed to marshal proto message: " + err.Error()) 47 | } 48 | 49 | return &ResponseBuilder{ 50 | body: string(bytes), 51 | } 52 | } 53 | 54 | func Fault(fault wiremock.Fault) *ResponseBuilder { 55 | return &ResponseBuilder{ 56 | fault: &fault, 57 | } 58 | } 59 | 60 | func (b *ResponseBuilder) WithDelay(delay wiremock.DelayInterface) *ResponseBuilder { 61 | b.delay = delay 62 | return b 63 | } 64 | 65 | func (b *ResponseBuilder) Build() wiremock.Response { 66 | response := wiremock.OK().WithHeader(responseStatusName, grpcCodeToWireMockCode(b.grpcResponseStatus)) 67 | 68 | if b.grpcStatusReason != nil { 69 | return response.WithHeader(responseStatusReason, *b.grpcStatusReason) 70 | } 71 | 72 | if b.fault != nil { 73 | return response.WithFault(*b.fault) 74 | } 75 | 76 | if b.delay != nil { 77 | response = response.WithDelay(b.delay) 78 | } 79 | 80 | return response.WithBody(b.body) 81 | } 82 | 83 | func grpcCodeToWireMockCode(code codes.Code) string { 84 | if code == codes.Canceled { 85 | return "CANCELLED" 86 | } 87 | 88 | re := regexp.MustCompile("([a-z0-9])([A-Z])") 89 | snake := re.ReplaceAllString(code.String(), "${1}_${2}") 90 | return strings.ToUpper(snake) 91 | } 92 | -------------------------------------------------------------------------------- /string_value_matcher_test.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStringValueMatcher_AddPrefixToMatcher(t *testing.T) { 8 | testCases := []struct { 9 | name string 10 | strategy ParamMatchingStrategy 11 | value string 12 | prefix string 13 | expected string 14 | }{ 15 | { 16 | name: "EqualTo - Add prefix to the value", 17 | strategy: ParamEqualTo, 18 | value: "abc", 19 | prefix: "pre_", 20 | expected: "pre_abc", 21 | }, 22 | { 23 | name: "Matches - Add prefix to regex value without start anchor", 24 | strategy: ParamMatches, 25 | value: "abc", 26 | prefix: "pre_", 27 | expected: "^pre_abc", 28 | }, 29 | { 30 | name: "Matches - Add prefix to regex value with start anchor", 31 | strategy: ParamMatches, 32 | value: "^abc", 33 | prefix: "pre_", 34 | expected: "^pre_abc", 35 | }, 36 | { 37 | name: "Matches - Add prefix to regex value with end anchor", 38 | strategy: ParamMatches, 39 | value: "t?o?ken$", 40 | prefix: "pre_", 41 | expected: "^pre_t?o?ken$", 42 | }, 43 | { 44 | name: "Matches - Should add prefix to wildcard regex", 45 | strategy: ParamMatches, 46 | value: ".*", 47 | prefix: "pre_", 48 | expected: "^pre_.*", 49 | }, 50 | { 51 | name: "Matches - Should add prefix to empty regex", 52 | strategy: ParamMatches, 53 | value: "", 54 | prefix: "pre_", 55 | expected: "^pre_", 56 | }, 57 | { 58 | name: "DoesNotMatch - Add prefix to regex value without start anchor", 59 | strategy: ParamDoesNotMatch, 60 | value: "abc", 61 | prefix: "pre_", 62 | expected: "^pre_abc", 63 | }, 64 | { 65 | name: "DoesNotMatch - Add prefix to regex value with start anchor", 66 | strategy: ParamDoesNotMatch, 67 | value: "^abc", 68 | prefix: "pre_", 69 | expected: "^pre_abc", 70 | }, 71 | { 72 | name: "DoesNotMatch - wildcard regex", 73 | strategy: ParamDoesNotMatch, 74 | value: ".*", 75 | prefix: "pre_", 76 | expected: "^pre_.*", 77 | }, 78 | } 79 | 80 | for _, tc := range testCases { 81 | t.Run(tc.name, func(t *testing.T) { 82 | matcher := StringValueMatcher{ 83 | strategy: tc.strategy, 84 | value: tc.value, 85 | } 86 | 87 | modifiedMatcher := matcher.addPrefixToMatcher(tc.prefix) 88 | 89 | if modifiedMatcher.(StringValueMatcher).value != tc.expected { 90 | t.Errorf("Expected: %s, Got: %s", tc.expected, modifiedMatcher.(StringValueMatcher).value) 91 | } 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wiremock/go-wiremock 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/google/uuid v1.6.0 7 | github.com/testcontainers/testcontainers-go v0.28.0 8 | ) 9 | 10 | require ( 11 | dario.cat/mergo v1.0.0 // indirect 12 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect 13 | github.com/Microsoft/go-winio v0.6.1 // indirect 14 | github.com/Microsoft/hcsshim v0.11.4 // indirect 15 | github.com/cenkalti/backoff/v4 v4.2.1 // indirect 16 | github.com/containerd/containerd v1.7.12 // indirect 17 | github.com/containerd/log v0.1.0 // indirect 18 | github.com/cpuguy83/dockercfg v0.3.1 // indirect 19 | github.com/distribution/reference v0.5.0 // indirect 20 | github.com/docker/docker v25.0.2+incompatible // indirect 21 | github.com/docker/go-connections v0.5.0 // indirect 22 | github.com/docker/go-units v0.5.0 // indirect 23 | github.com/felixge/httpsnoop v1.0.3 // indirect 24 | github.com/go-logr/logr v1.2.4 // indirect 25 | github.com/go-logr/stdr v1.2.2 // indirect 26 | github.com/go-ole/go-ole v1.2.6 // indirect 27 | github.com/gogo/protobuf v1.3.2 // indirect 28 | github.com/golang/protobuf v1.5.3 // indirect 29 | github.com/klauspost/compress v1.16.0 // indirect 30 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 31 | github.com/magiconair/properties v1.8.7 // indirect 32 | github.com/moby/patternmatcher v0.6.0 // indirect 33 | github.com/moby/sys/sequential v0.5.0 // indirect 34 | github.com/moby/sys/user v0.1.0 // indirect 35 | github.com/moby/term v0.5.0 // indirect 36 | github.com/morikuni/aec v1.0.0 // indirect 37 | github.com/opencontainers/go-digest v1.0.0 // indirect 38 | github.com/opencontainers/image-spec v1.1.0-rc5 // indirect 39 | github.com/pkg/errors v0.9.1 // indirect 40 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 41 | github.com/shirou/gopsutil/v3 v3.23.12 // indirect 42 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 43 | github.com/sirupsen/logrus v1.9.3 // indirect 44 | github.com/tklauser/go-sysconf v0.3.12 // indirect 45 | github.com/tklauser/numcpus v0.6.1 // indirect 46 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 47 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect 48 | go.opentelemetry.io/otel v1.19.0 // indirect 49 | go.opentelemetry.io/otel/metric v1.19.0 // indirect 50 | go.opentelemetry.io/otel/trace v1.19.0 // indirect 51 | golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect 52 | golang.org/x/mod v0.11.0 // indirect 53 | golang.org/x/sys v0.16.0 // indirect 54 | golang.org/x/tools v0.10.0 // indirect 55 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect 56 | google.golang.org/grpc v1.58.3 // indirect 57 | google.golang.org/protobuf v1.31.0 // indirect 58 | ) 59 | -------------------------------------------------------------------------------- /webhook.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | type WebhookInterface interface { 9 | json.Marshaler 10 | WithName(name string) WebhookInterface 11 | ParseWebhook() map[string]interface{} 12 | } 13 | 14 | type Webhook struct { 15 | name string 16 | parameters webhookParameters 17 | } 18 | 19 | type webhookParameters struct { 20 | method string 21 | url string 22 | body string 23 | headers map[string]string 24 | delay DelayInterface 25 | } 26 | 27 | func (w webhookParameters) MarshalJSON() ([]byte, error) { 28 | jsonMap := map[string]interface{}{ 29 | "method": w.method, 30 | "url": w.url, 31 | "body": w.body, 32 | "headers": w.headers, 33 | "delay": w.delay.ParseDelay(), 34 | } 35 | 36 | return json.Marshal(jsonMap) 37 | } 38 | 39 | // WithName sets the name of the webhook and returns the webhook. 40 | func (w Webhook) WithName(name string) WebhookInterface { 41 | w.name = name 42 | return w 43 | } 44 | 45 | // ParseWebhook returns a map representation of the webhook. 46 | func (w Webhook) ParseWebhook() map[string]interface{} { 47 | return map[string]interface{}{ 48 | "name": w.name, 49 | "parameters": w.parameters, 50 | } 51 | } 52 | 53 | // MarshalJSON implements the json.Marshaler interface. 54 | func (w Webhook) MarshalJSON() ([]byte, error) { 55 | return json.Marshal(w.ParseWebhook()) 56 | } 57 | 58 | // WithMethod sets the HTTP method of the webhook. 59 | func (w Webhook) WithMethod(method string) Webhook { 60 | w.parameters.method = method 61 | return w 62 | } 63 | 64 | // WithURL sets the URL of the webhook. 65 | func (w Webhook) WithURL(url string) Webhook { 66 | w.parameters.url = url 67 | return w 68 | } 69 | 70 | // WithHeader sets a header of the webhook. 71 | func (w Webhook) WithHeader(key string, value string) Webhook { 72 | if w.parameters.headers == nil { 73 | w.parameters.headers = make(map[string]string) 74 | } 75 | 76 | w.parameters.headers[key] = value 77 | 78 | return w 79 | } 80 | 81 | // WithBody sets the body of the webhook. 82 | func (w Webhook) WithBody(body string) Webhook { 83 | w.parameters.body = body 84 | return w 85 | } 86 | 87 | // WithDelay sets the delay of the webhook. 88 | func (w Webhook) WithDelay(delay DelayInterface) Webhook { 89 | w.parameters.delay = delay 90 | return w 91 | } 92 | 93 | // WithFixedDelay sets the fixed delay of the webhook. 94 | func (w Webhook) WithFixedDelay(delay time.Duration) Webhook { 95 | w.parameters.delay = NewFixedDelay(delay) 96 | return w 97 | } 98 | 99 | // WithLogNormalRandomDelay sets the log normal delay of the webhook. 100 | func (w Webhook) WithLogNormalRandomDelay(median time.Duration, sigma float64) Webhook { 101 | w.parameters.delay = NewLogNormalRandomDelay(median, sigma) 102 | return w 103 | } 104 | 105 | // WithUniformRandomDelay sets the uniform random delay of the webhook. 106 | func (w Webhook) WithUniformRandomDelay(lower, upper time.Duration) Webhook { 107 | w.parameters.delay = NewUniformRandomDelay(lower, upper) 108 | return w 109 | } 110 | 111 | func NewWebhook() Webhook { 112 | return Webhook{} 113 | } 114 | -------------------------------------------------------------------------------- /testdata/expected-template-scenario.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "%s", 3 | "id": "%s", 4 | "priority": 1, 5 | "scenarioName": "Scenario", 6 | "requiredScenarioState": "Started", 7 | "newScenarioState": "Stopped", 8 | "request": { 9 | "method": "POST", 10 | "scheme": "http", 11 | "host": { 12 | "equalTo": "localhost" 13 | }, 14 | "port": 8080, 15 | "basicAuthCredentials": { 16 | "password": "password", 17 | "username": "username" 18 | }, 19 | "bodyPatterns": [ 20 | { 21 | "equalToJson": "{\"meta\": \"information\"}", 22 | "ignoreArrayOrder" : true, 23 | "ignoreExtraElements" : true 24 | }, 25 | { 26 | "contains": "information" 27 | } 28 | ], 29 | "formParameters": { 30 | "form1": { 31 | "equalTo": "value1" 32 | }, 33 | "form2": { 34 | "matches": "value2" 35 | } 36 | }, 37 | "multipartPatterns": [ 38 | { 39 | "matchingType": "ANY", 40 | "headers": { 41 | "Content-Disposition": { 42 | "contains": "name=\"info\"" 43 | }, 44 | "Content-Type": { 45 | "contains": "charset" 46 | } 47 | }, 48 | "bodyPatterns": [ 49 | { 50 | "equalToJson": "{}", 51 | "ignoreExtraElements": true 52 | } 53 | ] 54 | } 55 | ], 56 | "cookies": { 57 | "session": { 58 | "equalToXml": "\u003cxml\u003e" 59 | }, 60 | "absentcookie": { 61 | "absent": true 62 | } 63 | }, 64 | "headers": { 65 | "x-session": { 66 | "matches": "^\\S+@\\S+$" 67 | }, 68 | "x-absent": { 69 | "absent": true 70 | } 71 | }, 72 | "queryParameters": { 73 | "firstName": { 74 | "or": [ 75 | { 76 | "equalTo": "John" 77 | }, 78 | { 79 | "equalTo": "Jack" 80 | } 81 | ] 82 | }, 83 | "lastName": { 84 | "doesNotMatch": "Black" 85 | }, 86 | "nickname": { 87 | "equalTo": "johnBlack", 88 | "caseInsensitive": true 89 | }, 90 | "address": { 91 | "includes" : [ 92 | { 93 | "equalTo": "1" 94 | }, 95 | { 96 | "contains": "2" 97 | }, 98 | { 99 | "doesNotContain": "3" 100 | } 101 | ] 102 | }, 103 | "id": { 104 | "and": [ 105 | {"contains": "1"}, 106 | {"doesNotContain": "2"} 107 | ] 108 | } 109 | }, 110 | "urlPath": "/example" 111 | }, 112 | "response": { 113 | "body": "{\"code\": 400, \"detail\": \"detail\"}", 114 | "headers": { 115 | "Content-Type": "application/json" 116 | }, 117 | "status": 400, 118 | "delayDistribution": { 119 | "type": "fixed", 120 | "milliseconds": 5000 121 | }, 122 | "fault": "CONNECTION_RESET_BY_PEER" 123 | }, 124 | "postServeActions": [ 125 | { 126 | "name": "webhook", 127 | "parameters": { 128 | "method": "POST", 129 | "url": "http://my-target-host/callback", 130 | "headers": { 131 | "Content-Type": "application/json" 132 | }, 133 | "body": "{ \"result\": \"SUCCESS\" }", 134 | "delay": { 135 | "type": "fixed", 136 | "milliseconds": 1000 137 | } 138 | } 139 | } 140 | ] 141 | } -------------------------------------------------------------------------------- /journal/model.go: -------------------------------------------------------------------------------- 1 | package journal 2 | 3 | type Cookies map[string]string 4 | 5 | type Headers map[string]string 6 | 7 | type Params map[string]Param 8 | 9 | type Param struct { 10 | Key string `json:"key"` 11 | Values []string `json:"values"` 12 | } 13 | 14 | type GetAllRequestsResponse struct { 15 | Requests []GetRequestResponse `json:"requests,omitempty"` 16 | Meta Meta `json:"meta,omitempty"` 17 | RequestJournalDisabled bool `json:"requestJournalDisabled,omitempty"` 18 | } 19 | 20 | type GetRequestResponse struct { 21 | ID string `json:"id,omitempty"` 22 | Request Request `json:"request,omitempty"` 23 | ResponseDefinition ResponseDefinition `json:"responseDefinition,omitempty"` 24 | Response Response `json:"response,omitempty"` 25 | WasMatched bool `json:"wasMatched,omitempty"` 26 | Timing Timing `json:"timing,omitempty"` 27 | StubMapping StubMapping `json:"stubMapping,omitempty"` 28 | } 29 | 30 | type FindRequestsByCriteriaResponse struct { 31 | Requests []Request `json:"requests,omitempty"` 32 | } 33 | 34 | type FindUnmatchedRequestsResponse struct { 35 | Requests []Request `json:"requests,omitempty"` 36 | } 37 | 38 | type DeleteRequestByCriteriaResponse struct { 39 | Requests []GetRequestResponse `json:"serveEvents,omitempty"` 40 | } 41 | 42 | type Request struct { 43 | URL string `json:"url,omitempty"` 44 | AbsoluteURL string `json:"absoluteUrl,omitempty"` 45 | Method string `json:"method,omitempty"` 46 | ClientIP string `json:"clientIp,omitempty"` 47 | Headers Headers `json:"headers,omitempty"` 48 | Cookies Cookies `json:"cookies,omitempty"` 49 | BrowserProxyRequest bool `json:"browserProxyRequest,omitempty"` 50 | LoggedDate int64 `json:"loggedDate,omitempty"` 51 | BodyAsBase64 string `json:"bodyAsBase64,omitempty"` 52 | Body string `json:"body,omitempty"` 53 | Protocol string `json:"protocol,omitempty"` 54 | Scheme string `json:"scheme,omitempty"` 55 | LoggedDateString string `json:"loggedDateString,omitempty"` 56 | Host string `json:"host,omitempty"` 57 | Port int64 `json:"port,omitempty"` 58 | QueryParams Params `json:"queryParams,omitempty"` 59 | FormParams Params `json:"formParams,omitempty"` 60 | } 61 | 62 | type ResponseDefinition struct { 63 | Headers Headers `json:"headers,omitempty"` 64 | Body string `json:"body,omitempty"` 65 | Status int64 `json:"status,omitempty"` 66 | FromConfiguredStub bool `json:"fromConfiguredStub,omitempty"` 67 | } 68 | 69 | type Response struct { 70 | Headers Headers `json:"headers,omitempty"` 71 | BodyAsBase64 string `json:"bodyAsBase64,omitempty"` 72 | Body string `json:"body,omitempty"` 73 | Status int64 `json:"status,omitempty"` 74 | } 75 | 76 | type Timing struct { 77 | ServeTime int64 `json:"serveTime,omitempty"` 78 | TotalTime int64 `json:"totalTime,omitempty"` 79 | ProcessTime int64 `json:"processTime,omitempty"` 80 | ResponseSendTime int64 `json:"responseSendTime,omitempty"` 81 | AddedDelay int64 `json:"addedDelay,omitempty"` 82 | } 83 | 84 | type StubMapping struct { 85 | ID string `json:"id,omitempty"` 86 | Request StubMappingRequest `json:"request,omitempty"` 87 | Response ResponseDefinition `json:"response,omitempty"` 88 | UUID string `json:"uuid,omitempty"` 89 | } 90 | 91 | type StubMappingRequest struct { 92 | Method string `json:"method,omitempty"` 93 | URL string `json:"url,omitempty"` 94 | URLPattern string `json:"urlPattern,omitempty"` 95 | URLPath string `json:"urlPath,omitempty"` 96 | URLPathPattern string `json:"urlPathPattern,omitempty"` 97 | URLPathTemplate string `json:"urlPathTemplate,omitempty"` 98 | } 99 | 100 | type Meta struct { 101 | Total int64 `json:"total,omitempty"` 102 | } 103 | 104 | type ServeEvent struct { 105 | Request Request `json:"request,omitempty"` 106 | StubMapping StubMapping `json:"stubMapping,omitempty"` 107 | WasMatched bool `json:"wasMatched,omitempty"` 108 | Response Response `json:"response,omitempty"` 109 | Timing Timing `json:"timing,omitempty"` 110 | ID string `json:"id,omitempty"` 111 | ResponseDefinition ResponseDefinition `json:"responseDefinition,omitempty"` 112 | } 113 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // A Request is the part of StubRule describing the matching of the http request 8 | type Request struct { 9 | urlMatcher URLMatcherInterface 10 | method string 11 | host BasicParamMatcher 12 | port *int64 13 | scheme *string 14 | headers map[string]MatcherInterface 15 | queryParams map[string]MatcherInterface 16 | pathParams map[string]MatcherInterface 17 | cookies map[string]BasicParamMatcher 18 | formParameters map[string]BasicParamMatcher 19 | bodyPatterns []BasicParamMatcher 20 | multipartPatterns []MultipartPatternInterface 21 | basicAuthCredentials *struct { 22 | username string 23 | password string 24 | } 25 | } 26 | 27 | // NewRequest constructs minimum possible Request 28 | func NewRequest(method string, urlMatcher URLMatcherInterface) *Request { 29 | return &Request{ 30 | method: method, 31 | urlMatcher: urlMatcher, 32 | } 33 | } 34 | 35 | // WithPort is fluent-setter for port 36 | func (r *Request) WithPort(port int64) *Request { 37 | r.port = &port 38 | return r 39 | } 40 | 41 | // WithScheme is fluent-setter for scheme 42 | func (r *Request) WithScheme(scheme string) *Request { 43 | r.scheme = &scheme 44 | return r 45 | } 46 | 47 | // WithHost is fluent-setter for host 48 | func (r *Request) WithHost(host BasicParamMatcher) *Request { 49 | r.host = host 50 | return r 51 | } 52 | 53 | // WithMethod is fluent-setter for http verb 54 | func (r *Request) WithMethod(method string) *Request { 55 | r.method = method 56 | return r 57 | } 58 | 59 | // WithURLMatched is fluent-setter url matcher 60 | func (r *Request) WithURLMatched(urlMatcher URLMatcherInterface) *Request { 61 | r.urlMatcher = urlMatcher 62 | return r 63 | } 64 | 65 | // WithBodyPattern adds body pattern to list 66 | func (r *Request) WithBodyPattern(matcher BasicParamMatcher) *Request { 67 | r.bodyPatterns = append(r.bodyPatterns, matcher) 68 | return r 69 | } 70 | 71 | // WithFormParameter adds form parameter to list 72 | func (r *Request) WithFormParameter(name string, matcher BasicParamMatcher) *Request { 73 | if r.formParameters == nil { 74 | r.formParameters = make(map[string]BasicParamMatcher, 1) 75 | } 76 | r.formParameters[name] = matcher 77 | return r 78 | } 79 | 80 | // WithMultipartPattern adds multipart pattern to list 81 | func (r *Request) WithMultipartPattern(pattern *MultipartPattern) *Request { 82 | r.multipartPatterns = append(r.multipartPatterns, pattern) 83 | return r 84 | } 85 | 86 | // WithBasicAuth adds basic auth credentials to Request 87 | func (r *Request) WithBasicAuth(username, password string) *Request { 88 | r.basicAuthCredentials = &struct { 89 | username string 90 | password string 91 | }{ 92 | username: username, 93 | password: password, 94 | } 95 | return r 96 | } 97 | 98 | // WithQueryParam add param to query param list 99 | func (r *Request) WithQueryParam(param string, matcher MatcherInterface) *Request { 100 | if r.queryParams == nil { 101 | r.queryParams = map[string]MatcherInterface{} 102 | } 103 | 104 | r.queryParams[param] = matcher 105 | return r 106 | } 107 | 108 | // WithPathParam add param to path param list 109 | func (r *Request) WithPathParam(param string, matcher MatcherInterface) *Request { 110 | if r.pathParams == nil { 111 | r.pathParams = map[string]MatcherInterface{} 112 | } 113 | 114 | r.pathParams[param] = matcher 115 | return r 116 | } 117 | 118 | // WithHeader add header to header list 119 | func (r *Request) WithHeader(header string, matcher MatcherInterface) *Request { 120 | if r.headers == nil { 121 | r.headers = map[string]MatcherInterface{} 122 | } 123 | 124 | r.headers[header] = matcher 125 | return r 126 | } 127 | 128 | // WithCookie is fluent-setter for cookie 129 | func (r *Request) WithCookie(cookie string, matcher BasicParamMatcher) *Request { 130 | if r.cookies == nil { 131 | r.cookies = map[string]BasicParamMatcher{} 132 | } 133 | 134 | r.cookies[cookie] = matcher 135 | return r 136 | } 137 | 138 | // MarshalJSON gives valid JSON or error. 139 | func (r *Request) MarshalJSON() ([]byte, error) { 140 | request := map[string]interface{}{ 141 | "method": r.method, 142 | string(r.urlMatcher.Strategy()): r.urlMatcher.Value(), 143 | } 144 | 145 | if r.scheme != nil { 146 | request["scheme"] = r.scheme 147 | } 148 | 149 | if r.host != nil { 150 | request["host"] = r.host 151 | } 152 | 153 | if r.port != nil { 154 | request["port"] = r.port 155 | } 156 | 157 | if len(r.bodyPatterns) > 0 { 158 | request["bodyPatterns"] = r.bodyPatterns 159 | } 160 | if len(r.formParameters) > 0 { 161 | request["formParameters"] = r.formParameters 162 | } 163 | if len(r.multipartPatterns) > 0 { 164 | request["multipartPatterns"] = r.multipartPatterns 165 | } 166 | if len(r.headers) > 0 { 167 | request["headers"] = r.headers 168 | } 169 | if len(r.cookies) > 0 { 170 | request["cookies"] = r.cookies 171 | } 172 | if len(r.queryParams) > 0 { 173 | request["queryParameters"] = r.queryParams 174 | } 175 | if len(r.pathParams) > 0 { 176 | request["pathParameters"] = r.pathParams 177 | } 178 | 179 | if r.basicAuthCredentials != nil { 180 | request["basicAuthCredentials"] = map[string]string{ 181 | "password": r.basicAuthCredentials.password, 182 | "username": r.basicAuthCredentials.username, 183 | } 184 | } 185 | 186 | return json.Marshal(request) 187 | } 188 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | type Fault string 9 | 10 | const ( 11 | FaultEmptyResponse Fault = "EMPTY_RESPONSE" 12 | FaultMalformedResponseChunk Fault = "MALFORMED_RESPONSE_CHUNK" 13 | FaultRandomDataThenClose Fault = "RANDOM_DATA_THEN_CLOSE" 14 | FaultConnectionResetByPeer Fault = "CONNECTION_RESET_BY_PEER" 15 | ) 16 | 17 | type ResponseInterface interface { 18 | ParseResponse() map[string]interface{} 19 | } 20 | 21 | type Response struct { 22 | body *string 23 | base64Body []byte 24 | bodyFileName *string 25 | jsonBody interface{} 26 | headers map[string]string 27 | status int64 28 | delayDistribution DelayInterface 29 | chunkedDribbleDelay *chunkedDribbleDelay 30 | fault *Fault 31 | transformers []string 32 | transformerParameters map[string]string 33 | } 34 | 35 | func NewResponse() Response { 36 | return Response{ 37 | status: http.StatusOK, 38 | } 39 | } 40 | 41 | func OK() Response { 42 | return NewResponse().WithStatus(http.StatusOK) 43 | } 44 | 45 | // WithLogNormalRandomDelay sets log normal random delay for response 46 | func (r Response) WithLogNormalRandomDelay(median time.Duration, sigma float64) Response { 47 | r.delayDistribution = NewLogNormalRandomDelay(median, sigma) 48 | return r 49 | } 50 | 51 | // WithUniformRandomDelay sets uniform random delay for response 52 | func (r Response) WithUniformRandomDelay(lower, upper time.Duration) Response { 53 | r.delayDistribution = NewUniformRandomDelay(lower, upper) 54 | return r 55 | } 56 | 57 | // WithFixedDelay sets fixed delay milliseconds for response 58 | func (r Response) WithFixedDelay(delay time.Duration) Response { 59 | r.delayDistribution = NewFixedDelay(delay) 60 | return r 61 | } 62 | 63 | // WithDelay sets delay for response 64 | func (r Response) WithDelay(delay DelayInterface) Response { 65 | r.delayDistribution = delay 66 | return r 67 | } 68 | 69 | // WithChunkedDribbleDelay sets chunked dribble delay for response 70 | func (r Response) WithChunkedDribbleDelay(numberOfChunks int64, totalDuration time.Duration) Response { 71 | r.chunkedDribbleDelay = &chunkedDribbleDelay{ 72 | numberOfChunks: numberOfChunks, 73 | totalDuration: totalDuration.Milliseconds(), 74 | } 75 | 76 | return r 77 | } 78 | 79 | // WithStatus sets status for response 80 | func (r Response) WithStatus(status int64) Response { 81 | r.status = status 82 | return r 83 | } 84 | 85 | // WithHeader sets header for response 86 | func (r Response) WithHeader(key, value string) Response { 87 | if r.headers == nil { 88 | r.headers = make(map[string]string) 89 | } 90 | 91 | r.headers[key] = value 92 | 93 | return r 94 | } 95 | 96 | // WithHeaders sets headers for response 97 | func (r Response) WithHeaders(headers map[string]string) Response { 98 | r.headers = headers 99 | return r 100 | } 101 | 102 | func (r Response) WithFault(fault Fault) Response { 103 | r.fault = &fault 104 | return r 105 | } 106 | 107 | // WithBody sets body for response 108 | func (r Response) WithBody(body string) Response { 109 | r.body = &body 110 | return r 111 | } 112 | 113 | // WithBinaryBody sets binary body for response 114 | func (r Response) WithBinaryBody(body []byte) Response { 115 | r.base64Body = body 116 | return r 117 | } 118 | 119 | // WithJSONBody sets json body for response 120 | func (r Response) WithJSONBody(body interface{}) Response { 121 | r.jsonBody = body 122 | return r 123 | } 124 | 125 | // WithBodyFile sets body file name for response 126 | func (r Response) WithBodyFile(fileName string) Response { 127 | r.bodyFileName = &fileName 128 | return r 129 | } 130 | 131 | // WithTransformers sets transformers for response 132 | func (r Response) WithTransformers(transformers ...string) Response { 133 | r.transformers = transformers 134 | return r 135 | } 136 | 137 | // WithTransformerParameter sets transformer parameters for response 138 | func (r Response) WithTransformerParameter(key, value string) Response { 139 | if r.transformerParameters == nil { 140 | r.transformerParameters = make(map[string]string) 141 | } 142 | 143 | r.transformerParameters[key] = value 144 | 145 | return r 146 | } 147 | 148 | // WithTransformerParameters sets transformer parameters for response 149 | func (r Response) WithTransformerParameters(transformerParameters map[string]string) Response { 150 | r.transformerParameters = transformerParameters 151 | return r 152 | } 153 | 154 | func (r Response) ParseResponse() map[string]interface{} { 155 | jsonMap := map[string]interface{}{ 156 | "status": r.status, 157 | } 158 | 159 | if r.body != nil { 160 | jsonMap["body"] = *r.body 161 | } 162 | 163 | if r.base64Body != nil { 164 | jsonMap["base64Body"] = r.base64Body 165 | } 166 | 167 | if r.bodyFileName != nil { 168 | jsonMap["bodyFileName"] = *r.bodyFileName 169 | } 170 | 171 | if r.jsonBody != nil { 172 | jsonMap["jsonBody"] = r.jsonBody 173 | } 174 | 175 | if r.headers != nil { 176 | jsonMap["headers"] = r.headers 177 | } 178 | 179 | if r.delayDistribution != nil { 180 | jsonMap["delayDistribution"] = r.delayDistribution.ParseDelay() 181 | } 182 | 183 | if r.chunkedDribbleDelay != nil { 184 | jsonMap["chunkedDribbleDelay"] = r.chunkedDribbleDelay 185 | } 186 | 187 | if r.fault != nil { 188 | jsonMap["fault"] = *r.fault 189 | } 190 | 191 | if r.transformers != nil { 192 | jsonMap["transformers"] = r.transformers 193 | } 194 | 195 | if r.transformerParameters != nil { 196 | jsonMap["transformerParameters"] = r.transformerParameters 197 | } 198 | 199 | return jsonMap 200 | } 201 | -------------------------------------------------------------------------------- /grpc/testdata/greeting_service.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.36.6 4 | // protoc v5.29.3 5 | // source: greeting_service.proto 6 | 7 | package testdata 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | unsafe "unsafe" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | type GreetingRequest struct { 25 | state protoimpl.MessageState `protogen:"open.v1"` 26 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 27 | unknownFields protoimpl.UnknownFields 28 | sizeCache protoimpl.SizeCache 29 | } 30 | 31 | func (x *GreetingRequest) Reset() { 32 | *x = GreetingRequest{} 33 | mi := &file_greeting_service_proto_msgTypes[0] 34 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 35 | ms.StoreMessageInfo(mi) 36 | } 37 | 38 | func (x *GreetingRequest) String() string { 39 | return protoimpl.X.MessageStringOf(x) 40 | } 41 | 42 | func (*GreetingRequest) ProtoMessage() {} 43 | 44 | func (x *GreetingRequest) ProtoReflect() protoreflect.Message { 45 | mi := &file_greeting_service_proto_msgTypes[0] 46 | if x != nil { 47 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 48 | if ms.LoadMessageInfo() == nil { 49 | ms.StoreMessageInfo(mi) 50 | } 51 | return ms 52 | } 53 | return mi.MessageOf(x) 54 | } 55 | 56 | // Deprecated: Use GreetingRequest.ProtoReflect.Descriptor instead. 57 | func (*GreetingRequest) Descriptor() ([]byte, []int) { 58 | return file_greeting_service_proto_rawDescGZIP(), []int{0} 59 | } 60 | 61 | func (x *GreetingRequest) GetName() string { 62 | if x != nil { 63 | return x.Name 64 | } 65 | return "" 66 | } 67 | 68 | type GreetingResponse struct { 69 | state protoimpl.MessageState `protogen:"open.v1"` 70 | Greeting string `protobuf:"bytes,1,opt,name=greeting,proto3" json:"greeting,omitempty"` 71 | unknownFields protoimpl.UnknownFields 72 | sizeCache protoimpl.SizeCache 73 | } 74 | 75 | func (x *GreetingResponse) Reset() { 76 | *x = GreetingResponse{} 77 | mi := &file_greeting_service_proto_msgTypes[1] 78 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 79 | ms.StoreMessageInfo(mi) 80 | } 81 | 82 | func (x *GreetingResponse) String() string { 83 | return protoimpl.X.MessageStringOf(x) 84 | } 85 | 86 | func (*GreetingResponse) ProtoMessage() {} 87 | 88 | func (x *GreetingResponse) ProtoReflect() protoreflect.Message { 89 | mi := &file_greeting_service_proto_msgTypes[1] 90 | if x != nil { 91 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 92 | if ms.LoadMessageInfo() == nil { 93 | ms.StoreMessageInfo(mi) 94 | } 95 | return ms 96 | } 97 | return mi.MessageOf(x) 98 | } 99 | 100 | // Deprecated: Use GreetingResponse.ProtoReflect.Descriptor instead. 101 | func (*GreetingResponse) Descriptor() ([]byte, []int) { 102 | return file_greeting_service_proto_rawDescGZIP(), []int{1} 103 | } 104 | 105 | func (x *GreetingResponse) GetGreeting() string { 106 | if x != nil { 107 | return x.Greeting 108 | } 109 | return "" 110 | } 111 | 112 | var File_greeting_service_proto protoreflect.FileDescriptor 113 | 114 | const file_greeting_service_proto_rawDesc = "" + 115 | "\n" + 116 | "\x16greeting_service.proto\x12\x17com.example.greeting.v1\"%\n" + 117 | "\x0fGreetingRequest\x12\x12\n" + 118 | "\x04name\x18\x01 \x01(\tR\x04name\".\n" + 119 | "\x10GreetingResponse\x12\x1a\n" + 120 | "\bgreeting\x18\x01 \x01(\tR\bgreeting2o\n" + 121 | "\x0fGreetingService\x12\\\n" + 122 | "\x05greet\x12(.com.example.greeting.v1.GreetingRequest\x1a).com.example.greeting.v1.GreetingResponseB\x11P\x01Z\rgrpc/testdatab\x06proto3" 123 | 124 | var ( 125 | file_greeting_service_proto_rawDescOnce sync.Once 126 | file_greeting_service_proto_rawDescData []byte 127 | ) 128 | 129 | func file_greeting_service_proto_rawDescGZIP() []byte { 130 | file_greeting_service_proto_rawDescOnce.Do(func() { 131 | file_greeting_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_greeting_service_proto_rawDesc), len(file_greeting_service_proto_rawDesc))) 132 | }) 133 | return file_greeting_service_proto_rawDescData 134 | } 135 | 136 | var file_greeting_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 137 | var file_greeting_service_proto_goTypes = []any{ 138 | (*GreetingRequest)(nil), // 0: com.example.greeting.v1.GreetingRequest 139 | (*GreetingResponse)(nil), // 1: com.example.greeting.v1.GreetingResponse 140 | } 141 | var file_greeting_service_proto_depIdxs = []int32{ 142 | 0, // 0: com.example.greeting.v1.GreetingService.greet:input_type -> com.example.greeting.v1.GreetingRequest 143 | 1, // 1: com.example.greeting.v1.GreetingService.greet:output_type -> com.example.greeting.v1.GreetingResponse 144 | 1, // [1:2] is the sub-list for method output_type 145 | 0, // [0:1] is the sub-list for method input_type 146 | 0, // [0:0] is the sub-list for extension type_name 147 | 0, // [0:0] is the sub-list for extension extendee 148 | 0, // [0:0] is the sub-list for field type_name 149 | } 150 | 151 | func init() { file_greeting_service_proto_init() } 152 | func file_greeting_service_proto_init() { 153 | if File_greeting_service_proto != nil { 154 | return 155 | } 156 | type x struct{} 157 | out := protoimpl.TypeBuilder{ 158 | File: protoimpl.DescBuilder{ 159 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 160 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_greeting_service_proto_rawDesc), len(file_greeting_service_proto_rawDesc)), 161 | NumEnums: 0, 162 | NumMessages: 2, 163 | NumExtensions: 0, 164 | NumServices: 1, 165 | }, 166 | GoTypes: file_greeting_service_proto_goTypes, 167 | DependencyIndexes: file_greeting_service_proto_depIdxs, 168 | MessageInfos: file_greeting_service_proto_msgTypes, 169 | }.Build() 170 | File_greeting_service_proto = out.File 171 | file_greeting_service_proto_goTypes = nil 172 | file_greeting_service_proto_depIdxs = nil 173 | } 174 | -------------------------------------------------------------------------------- /string_value_matcher.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | ) 8 | 9 | type MatcherInterface interface { 10 | json.Marshaler 11 | ParseMatcher() map[string]interface{} 12 | } 13 | 14 | type BasicParamMatcher interface { 15 | json.Marshaler 16 | ParseMatcher() map[string]interface{} 17 | Or(stringMatcher BasicParamMatcher) BasicParamMatcher 18 | And(stringMatcher BasicParamMatcher) BasicParamMatcher 19 | } 20 | 21 | type StringValueMatcher struct { 22 | strategy ParamMatchingStrategy 23 | value string 24 | flags []string 25 | } 26 | 27 | // MarshalJSON returns the JSON encoding of the matcher. 28 | func (m StringValueMatcher) MarshalJSON() ([]byte, error) { 29 | return json.Marshal(m.ParseMatcher()) 30 | } 31 | 32 | // ParseMatcher returns the map representation of the structure. 33 | func (m StringValueMatcher) ParseMatcher() map[string]interface{} { 34 | jsonMap := make(map[string]interface{}, 1+len(m.flags)) 35 | if m.strategy != "" { 36 | jsonMap[string(m.strategy)] = m.value 37 | } 38 | 39 | for _, flag := range m.flags { 40 | jsonMap[flag] = true 41 | } 42 | 43 | return jsonMap 44 | } 45 | 46 | // Or returns a logical OR of the two matchers. 47 | func (m StringValueMatcher) Or(matcher BasicParamMatcher) BasicParamMatcher { 48 | return Or(m, matcher) 49 | } 50 | 51 | // And returns a logical AND of the two matchers. 52 | func (m StringValueMatcher) And(matcher BasicParamMatcher) BasicParamMatcher { 53 | return And(m, matcher) 54 | } 55 | 56 | // addPrefixToMatcher adds prefix to matcher. 57 | // In case of "contains", "absent", "doesNotContain" prefix is not added as it doesn't affect the match result 58 | func (m StringValueMatcher) addPrefixToMatcher(prefix string) BasicParamMatcher { 59 | switch m.strategy { 60 | case ParamEqualTo, ParamEqualToJson, ParamEqualToXml, ParamMatchesJsonPath, ParamMatchesXPath: 61 | m.value = prefix + m.value 62 | case ParamMatches, ParamDoesNotMatch: 63 | if regexContainsStartAnchor(m.value) { 64 | m.value = m.value[1:] 65 | } 66 | m.value = fmt.Sprintf("^%s", prefix) + m.value 67 | } 68 | return m 69 | } 70 | 71 | // NewStringValueMatcher creates a new StringValueMatcher. 72 | func NewStringValueMatcher(strategy ParamMatchingStrategy, value string, flags ...string) StringValueMatcher { 73 | return StringValueMatcher{ 74 | strategy: strategy, 75 | value: value, 76 | flags: flags, 77 | } 78 | } 79 | 80 | // EqualTo returns a matcher that matches when the parameter equals the specified value. 81 | func EqualTo(value string) BasicParamMatcher { 82 | return NewStringValueMatcher(ParamEqualTo, value) 83 | } 84 | 85 | // EqualToIgnoreCase returns a matcher that matches when the parameter equals the specified value, ignoring case. 86 | func EqualToIgnoreCase(value string) BasicParamMatcher { 87 | return NewStringValueMatcher(ParamEqualTo, value, "caseInsensitive") 88 | } 89 | 90 | // Matching returns a matcher that matches when the parameter matches the specified regular expression. 91 | func Matching(param string) BasicParamMatcher { 92 | return NewStringValueMatcher(ParamMatches, param) 93 | } 94 | 95 | // EqualToXml returns a matcher that matches when the parameter is equal to the specified XML. 96 | func EqualToXml(param string) BasicParamMatcher { 97 | return NewStringValueMatcher(ParamEqualToXml, param) 98 | } 99 | 100 | // EqualToJson returns a matcher that matches when the parameter is equal to the specified JSON. 101 | func EqualToJson(param string, equalJsonFlags ...EqualFlag) BasicParamMatcher { 102 | flags := make([]string, len(equalJsonFlags)) 103 | for i, flag := range equalJsonFlags { 104 | flags[i] = string(flag) 105 | } 106 | 107 | return NewStringValueMatcher(ParamEqualToJson, param, flags...) 108 | } 109 | 110 | // MustEqualToJson returns a matcher that matches when the parameter is equal to the specified JSON. 111 | // This method panics if param cannot be marshaled to JSON. 112 | func MustEqualToJson(param any, equalJsonFlags ...EqualFlag) BasicParamMatcher { 113 | if str, ok := param.(string); ok { 114 | return EqualToJson(str, equalJsonFlags...) 115 | } 116 | 117 | if jsonParam, err := json.Marshal(param); err != nil { 118 | panic(fmt.Sprintf("Unable to marshal parameter to JSON: %v", err)) 119 | } else { 120 | return EqualToJson(string(jsonParam), equalJsonFlags...) 121 | } 122 | } 123 | 124 | // MatchingXPath returns a matcher that matches when the parameter matches the specified XPath. 125 | func MatchingXPath(param string) BasicParamMatcher { 126 | return NewStringValueMatcher(ParamMatchesXPath, param) 127 | } 128 | 129 | // MatchingJsonPath returns a matcher that matches when the parameter matches the specified JSON path. 130 | func MatchingJsonPath(param string) BasicParamMatcher { 131 | return NewStringValueMatcher(ParamMatchesJsonPath, param) 132 | } 133 | 134 | // NotMatching returns a matcher that matches when the parameter does not match the specified regular expression. 135 | func NotMatching(param string) BasicParamMatcher { 136 | return NewStringValueMatcher(ParamDoesNotMatch, param) 137 | } 138 | 139 | // Absent returns a matcher that matches when the parameter is absent. 140 | func Absent() BasicParamMatcher { 141 | return StringValueMatcher{ 142 | flags: []string{string(ParamAbsent)}, 143 | } 144 | } 145 | 146 | // Contains returns a matcher that matches when the parameter contains the specified value. 147 | func Contains(param string) BasicParamMatcher { 148 | return NewStringValueMatcher(ParamContains, param) 149 | } 150 | 151 | // NotContains returns a matcher that matches when the parameter does not contain the specified value. 152 | func NotContains(param string) BasicParamMatcher { 153 | return NewStringValueMatcher(ParamDoesNotContains, param) 154 | } 155 | 156 | // StartsWith returns a matcher that matches when the parameter starts with the specified prefix. 157 | // Matches also when prefix alone is the whole expression 158 | func StartsWith(prefix string) BasicParamMatcher { 159 | regex := fmt.Sprintf(`^%s\s*\S*`, regexp.QuoteMeta(prefix)) 160 | return NewStringValueMatcher(ParamMatches, regex) 161 | } 162 | 163 | type JSONSchemaMatcher struct { 164 | StringValueMatcher 165 | schemaVersion string 166 | } 167 | 168 | // MatchesJsonSchema returns a matcher that matches when the parameter matches the specified JSON schema. 169 | // Required wiremock version >= 3.0.0 170 | func MatchesJsonSchema(schema string, schemaVersion string) BasicParamMatcher { 171 | return JSONSchemaMatcher{ 172 | StringValueMatcher: NewStringValueMatcher(ParamMatchesJsonSchema, schema), 173 | schemaVersion: schemaVersion, 174 | } 175 | } 176 | 177 | // MarshalJSON returns the JSON encoding of the matcher. 178 | func (m JSONSchemaMatcher) MarshalJSON() ([]byte, error) { 179 | return json.Marshal(map[string]string{ 180 | string(m.strategy): m.value, 181 | "schemaVersion": m.schemaVersion, 182 | }) 183 | } 184 | 185 | func regexContainsStartAnchor(regex string) bool { 186 | return len(regex) > 0 && regex[0] == '^' 187 | } 188 | -------------------------------------------------------------------------------- /stub_rule_test.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "path/filepath" 9 | "reflect" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | const testDataDir = "testdata" 15 | 16 | func TestStubRule_ToJson(t *testing.T) { 17 | testCases := []struct { 18 | Name string 19 | StubRule *StubRule 20 | ExpectedFileName string 21 | }{ 22 | { 23 | Name: "BasicStubRule", 24 | StubRule: NewStubRule("PATCH", URLMatching("/example")), 25 | ExpectedFileName: "expected-template-basic.json", 26 | }, 27 | { 28 | Name: "StubRuleWithScenario", 29 | StubRule: Post(URLPathEqualTo("/example")). 30 | WithHost(EqualTo("localhost")). 31 | WithScheme("http"). 32 | WithPort(8080). 33 | WithQueryParam("firstName", EqualTo("John").Or(EqualTo("Jack"))). 34 | WithQueryParam("lastName", NotMatching("Black")). 35 | WithQueryParam("nickname", EqualToIgnoreCase("johnBlack")). 36 | WithQueryParam("address", Includes(EqualTo("1"), Contains("2"), NotContains("3"))). 37 | WithQueryParam("id", Contains("1").And(NotContains("2"))). 38 | WithBodyPattern(EqualToJson(`{"meta": "information"}`, IgnoreArrayOrder, IgnoreExtraElements)). 39 | WithBodyPattern(Contains("information")). 40 | WithFormParameter("form1", EqualTo("value1")). 41 | WithFormParameter("form2", Matching("value2")). 42 | WithMultipartPattern( 43 | NewMultipartPattern(). 44 | WithName("info"). 45 | WithHeader("Content-Type", Contains("charset")). 46 | WithBodyPattern(EqualToJson("{}", IgnoreExtraElements)), 47 | ). 48 | WithBasicAuth("username", "password"). 49 | WithHeader("x-absent", Absent()). 50 | WithCookie("absentcookie", Absent()). 51 | WithHeader("x-session", Matching("^\\S+@\\S+$")). 52 | WithCookie("session", EqualToXml("")). 53 | WillReturnResponse( 54 | NewResponse(). 55 | WithStatus(http.StatusBadRequest). 56 | WithHeader("Content-Type", "application/json"). 57 | WithBody(`{"code": 400, "detail": "detail"}`). 58 | WithFault(FaultConnectionResetByPeer). 59 | WithFixedDelay(time.Second*5), 60 | ). 61 | WithPostServeAction("webhook", NewWebhook(). 62 | WithMethod("POST"). 63 | WithURL("http://my-target-host/callback"). 64 | WithHeader("Content-Type", "application/json"). 65 | WithBody(`{ "result": "SUCCESS" }`). 66 | WithFixedDelay(time.Second)). 67 | AtPriority(1). 68 | InScenario("Scenario"). 69 | WhenScenarioStateIs("Started"). 70 | WillSetStateTo("Stopped"), 71 | ExpectedFileName: "expected-template-scenario.json", 72 | }, 73 | { 74 | Name: "MustEqualToJson", 75 | StubRule: NewStubRule("PATCH", URLMatching("/example")). 76 | WithBodyPattern(MustEqualToJson(map[string]interface{}{"meta": "information"}, IgnoreArrayOrder, IgnoreExtraElements)), 77 | ExpectedFileName: "must-equal-to-json.json", 78 | }, 79 | { 80 | Name: "StubRuleWithBearerToken_StartsWithMatcher", 81 | StubRule: Post(URLPathEqualTo("/example")). 82 | WithHost(EqualTo("localhost")). 83 | WithScheme("http"). 84 | WithPort(8080). 85 | WithBearerToken(StartsWith("token")). 86 | WillReturnResponse(OK()), 87 | ExpectedFileName: "expected-template-bearer-auth-startsWith.json", 88 | }, 89 | { 90 | Name: "StubRuleWithBearerToken_EqualToMatcher", 91 | StubRule: Post(URLPathEqualTo("/example")). 92 | WithHost(EqualTo("localhost")). 93 | WithScheme("http"). 94 | WithPort(8080). 95 | WithBearerToken(EqualTo("token")). 96 | WillReturnResponse(OK()), 97 | ExpectedFileName: "expected-template-bearer-auth-equalTo.json", 98 | }, 99 | { 100 | Name: "StubRuleWithBearerToken_ContainsMatcher", 101 | StubRule: Post(URLPathEqualTo("/example")). 102 | WithHost(EqualTo("localhost")). 103 | WithScheme("http"). 104 | WithPort(8080). 105 | WithBearerToken(Contains("token")). 106 | WillReturnResponse(OK()), 107 | ExpectedFileName: "expected-template-bearer-auth-contains.json", 108 | }, 109 | { 110 | Name: "StubRuleWithBearerToken_LogicalMatcher", 111 | StubRule: Post(URLPathEqualTo("/example")). 112 | WithHost(EqualTo("localhost")). 113 | WithScheme("http"). 114 | WithPort(8080). 115 | WithBearerToken(EqualTo("token123").And(StartsWith("token"))). 116 | WillReturnResponse(OK()), 117 | ExpectedFileName: "expected-template-bearer-auth-logicalMatcher.json", 118 | }, 119 | { 120 | Name: "NotLogicalMatcher", 121 | StubRule: Post(URLPathEqualTo("/example")). 122 | WithQueryParam("firstName", Not(EqualTo("John").Or(EqualTo("Jack")))). 123 | WillReturnResponse(OK()), 124 | ExpectedFileName: "not-logical-expression.json", 125 | }, 126 | { 127 | Name: "JsonSchemaMatcher", 128 | StubRule: Post(URLPathEqualTo("/example")). 129 | WithQueryParam("firstName", MatchesJsonSchema( 130 | `{ 131 | "type": "object", 132 | "required": [ 133 | "name" 134 | ], 135 | "properties": { 136 | "name": { 137 | "type": "string" 138 | }, 139 | "tag": { 140 | "type": "string" 141 | } 142 | } 143 | }`, 144 | "V202012", 145 | )). 146 | WillReturnResponse(OK()), 147 | ExpectedFileName: "matches-Json-schema.json", 148 | }, 149 | { 150 | Name: "URLPathTemplateMatcher", 151 | StubRule: Get(URLPathTemplate("/contacts/{contactId}/addresses/{addressId}")). 152 | WithPathParam("contactId", EqualTo("12345")). 153 | WithPathParam("addressId", EqualTo("99876")). 154 | WillReturnResponse(OK()), 155 | ExpectedFileName: "url-path-templating.json", 156 | }, 157 | { 158 | Name: "StubRuleWithScenarioWithTransformerParameters", 159 | StubRule: Get(URLPathTemplate("/templated")). 160 | WillReturnResponse( 161 | NewResponse(). 162 | WithStatus(http.StatusOK). 163 | WithTransformers("response-template"). 164 | WithTransformerParameter("MyCustomParameter", "Parameter Value")), 165 | ExpectedFileName: "expected-template-transformerParameters.json", 166 | }, 167 | } 168 | 169 | for _, tc := range testCases { 170 | t.Run(tc.Name, func(t *testing.T) { 171 | stubRule := tc.StubRule 172 | 173 | rawExpectedRequestBody, err := os.ReadFile(filepath.Join(testDataDir, tc.ExpectedFileName)) 174 | if err != nil { 175 | t.Fatalf("failed to read expected JSON file %s: %v", tc.ExpectedFileName, err) 176 | } 177 | 178 | var expected map[string]interface{} 179 | err = json.Unmarshal([]byte(fmt.Sprintf(string(rawExpectedRequestBody), stubRule.uuid, stubRule.uuid)), &expected) 180 | if err != nil { 181 | t.Fatalf("StubRule json.Unmarshal error: %v", err) 182 | } 183 | 184 | rawResult, err := json.Marshal(stubRule) 185 | if err != nil { 186 | t.Fatalf("StubRule json.Marshal error: %v", err) 187 | } 188 | 189 | var parsedResult map[string]interface{} 190 | err = json.Unmarshal(rawResult, &parsedResult) 191 | if err != nil { 192 | t.Fatalf("StubRule json.Unmarshal error: %v", err) 193 | } 194 | 195 | if !reflect.DeepEqual(parsedResult, expected) { 196 | t.Errorf("expected JSON:\n%v\nactual JSON:\n%v", parsedResult, expected) 197 | } 198 | }) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-wiremock 2 | 3 | [![GoDoc](https://godoc.org/github.com/wiremock/go-wiremock?status.svg)](http://godoc.org/github.com/wiremock/go-wiremock) 4 | [![Actions Status](https://github.com/wiremock/go-wiremock/workflows/build/badge.svg)](https://github.com/wiremock/go-wiremock/actions?query=workflow%3Abuild) 5 | [![Slack](https://img.shields.io/badge/slack.wiremock.org-%23wiremock—go-brightgreen?style=flat&logo=slack)](https://slack.wiremock.org/) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/wiremock/go-wiremock)](https://goreportcard.com/report/github.com/wiremock/go-wiremock) 7 | 8 | 9 | Go WireMock Logo 10 | 11 | 12 | --- 13 | 14 | 15 | 16 | 20 | 21 |
17 | WireMock Cloud Logo 18 | WireMock open source is supported by WireMock Cloud. Please consider trying it out if your team needs advanced capabilities such as OpenAPI, dynamic state, data sources and more. 19 |
22 | 23 | --- 24 | 25 | The Golang client library to stub API resources in [WireMock](https://wiremock.org) using its 26 | [REST API](https://wiremock.org/docs/api/). 27 | The project connects to the instance and allows 28 | setting up stubs and response templating, 29 | or using administrative API to extract observability data. 30 | 31 | Learn more: [Golang & WireMock Solutions page]( https://wiremock.org/docs/solutions/golang/) 32 | 33 | ## Documentation 34 | 35 | [![GoDoc](https://godoc.org/github.com/wiremock/go-wiremock?status.svg)](http://godoc.org/github.com/wiremock/go-wiremock) 36 | 37 | ## Compatibility 38 | 39 | The library was tested with the following distributions 40 | of WireMock: 41 | 42 | - WireMock 2.x - standalone deployments, including but not limited to official Docker images, Helm charts and the Java executable 43 | - WireMock 3.x Beta - partial support, some features are 44 | yet to be implemented. Contributions are welcome! 45 | - [WireMock Cloud](https://www.wiremock.io/product) - 46 | proprietary SaaS edition by WireMock Inc. 47 | 48 | Note that the CI pipelines run only against the official community distributions of WireMock. 49 | It may work for custom builds and other distributions. 50 | Should there be any issues, contact their vendors/maintainers. 51 | 52 | ## Usage 53 | 54 | Launch a standalone Docker instance: 55 | 56 | ```shell 57 | docker run -it --rm -p 8080:8080 wiremock/wiremock 58 | ``` 59 | 60 | Connect to it using the client library: 61 | 62 | ```go 63 | package main 64 | 65 | import ( 66 | "net/http" 67 | "testing" 68 | 69 | "github.com/wiremock/go-wiremock" 70 | ) 71 | 72 | func TestSome(t *testing.T) { 73 | wiremockClient := wiremock.NewClient("http://0.0.0.0:8080") 74 | defer wiremockClient.Reset() 75 | 76 | // stubbing POST http://0.0.0.0:8080/example 77 | wiremockClient.StubFor(wiremock.Post(wiremock.URLPathEqualTo("/example")). 78 | WithQueryParam("firstName", wiremock.EqualTo("John")). 79 | WithQueryParam("lastName", wiremock.NotMatching("Black")). 80 | WithBodyPattern(wiremock.EqualToJson(`{"meta": "information"}`)). 81 | WithHeader("x-session", wiremock.Matching("^\\S+fingerprint\\S+$")). 82 | WithBearerToken(wiremock.StartsWith("token")). 83 | WillReturnResponse( 84 | wiremock.NewResponse(). 85 | WithJSONBody(map[string]interface{}{ 86 | "code": 400, 87 | "detail": "detail", 88 | }). 89 | WithHeader("Content-Type", "application/json"). 90 | WithStatus(http.StatusBadRequest), 91 | ). 92 | AtPriority(1)) 93 | 94 | // scenario 95 | defer wiremockClient.ResetAllScenarios() 96 | wiremockClient.StubFor(wiremock.Get(wiremock.URLPathEqualTo("/status")). 97 | WillReturnResponse( 98 | wiremock.NewResponse(). 99 | WithJSONBody(map[string]interface{}{ 100 | "status": nil, 101 | }). 102 | WithHeader("Content-Type", "application/json"). 103 | WithStatus(http.StatusOK), 104 | ). 105 | InScenario("Set status"). 106 | WhenScenarioStateIs(wiremock.ScenarioStateStarted)) 107 | 108 | wiremockClient.StubFor(wiremock.Post(wiremock.URLPathEqualTo("/state")). 109 | WithBodyPattern(wiremock.EqualToJson(`{"status": "started"}`)). 110 | InScenario("Set status"). 111 | WillSetStateTo("Status started")) 112 | 113 | statusStub := wiremock.Get(wiremock.URLPathEqualTo("/status")). 114 | WillReturnResponse( 115 | wiremock.NewResponse(). 116 | WithJSONBody(map[string]interface{}{ 117 | "status": "started", 118 | }). 119 | WithHeader("Content-Type", "application/json"). 120 | WithStatus(http.StatusOK), 121 | ). 122 | InScenario("Set status"). 123 | WhenScenarioStateIs("Status started") 124 | wiremockClient.StubFor(statusStub) 125 | 126 | //testing code... 127 | 128 | verifyResult, _ := wiremockClient.Verify(statusStub.Request(), 1) 129 | if !verifyResult { 130 | //... 131 | } 132 | 133 | wiremockClient.DeleteStub(statusStub) 134 | } 135 | ``` 136 | ## gRPC 137 | You can mock grpc services using the library as well. 138 | 139 | ### Prerequisites 140 | 141 | ```bash 142 | # create descriptor file for wiremock 143 | protoc --include_imports --descriptor_set_out ./proto/services_nebius.dsc ./proto/greeting_service.proto 144 | 145 | # download the wiremock grpc extension 146 | curl -L https://github-registry-files.githubusercontent.com/694300421/0a0c0b80-089b-11f0-8fd1-f7e53ce5fce0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAVCODYLSA53PQK4ZA%2F20250609%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250609T202456Z&X-Amz-Expires=300&X-Amz-Signature=85f6bc85c1b86f2416ceb5c5031fde515f9f3003d612e2e375801e3dadde0d1c&X-Amz-SignedHeaders=host&response-content-disposition=filename%3Dwiremock-grpc-extension-standalone-0.10.0.jar&response-content-type=application%2Foctet-stream -o ./extensions/wiremock-grpc-0.10.0.jar 147 | 148 | # run wiremock with grpc extension 149 | docker run -it --rm -p 8080:8080 -v $(pwd)/extensions:/var/wiremock/extensions -v $(pwd)/proto:/home/wiremock/grpc wiremock/wiremock --verbose 150 | ``` 151 | 152 | ### Example of usage 153 | 154 | ```go 155 | package main 156 | 157 | import ( 158 | "google.golang.org/grpc/codes" 159 | 160 | "github.com/wiremock/go-wiremock" 161 | wiremockGRPC "github.com/wiremock/go-wiremock/grpc" 162 | proto "github.com/wiremock/go-wiremock/grpc/testdata" 163 | ) 164 | 165 | func main() { 166 | wiremockClient := wiremock.NewClient("http://0.0.0.0:8080") 167 | service := wiremockGRPC.NewService("com.example.greeting.v1.GreetingService", wiremockClient) 168 | 169 | service.StubFor( 170 | wiremockGRPC.Method("Greet"). 171 | WithRequestMessage(wiremockGRPC.EqualToMessage(&proto.GreetingRequest{ 172 | Name: "Tom", 173 | })). 174 | WillReturn(wiremockGRPC.Message(&proto.GreetingResponse{ 175 | Greeting: "Hello, Tom!", 176 | })), 177 | ) 178 | 179 | service.StubFor( 180 | wiremockGRPC.Method("Greet"). 181 | WithRequestMessage(wiremockGRPC.EqualToMessage(&proto.GreetingRequest{ 182 | Name: "Robert", 183 | })). 184 | WillReturn(wiremockGRPC.Error(codes.NotFound, "Robert not found")), 185 | ) 186 | 187 | service.StubFor( 188 | wiremockGRPC.Method("Greet"). 189 | WithRequestMessage(wiremockGRPC.EqualToMessage(&proto.GreetingRequest{ 190 | Name: "Richard", 191 | })). 192 | WillReturn(wiremockGRPC.Fault(wiremock.FaultConnectionResetByPeer)), 193 | ) 194 | } 195 | 196 | ``` 197 | 198 | ## Recording Stubs 199 | 200 | Alternatively, you can use `wiremock` to record stubs and play them back: 201 | 202 | ```go 203 | wiremockClient.StartRecording("https://my.saas.endpoint.com") 204 | defer wiremockClient.StopRecording() 205 | //… do some requests to Wiremock 206 | //… do some assertions using your Saas' SDK 207 | ``` 208 | 209 | ## Support for Authentication Schemes 210 | 211 | The library provides support for common authentication schemes, i.e.: Basic Authentication, API Token Authentication, Bearer Authentication, Digest Access Authentication. 212 | All of them are equivalent to manually specifying the "Authorization" header value with the appropriate prefix. 213 | E.g. `WithBearerToken(wiremock.EqualTo("token123")).` works the same as `WithHeader("Authorization", wiremock.EqualTo("Bearer token123")).`. 214 | 215 | ### Example of usage 216 | 217 | ```go 218 | 219 | basicAuthStub := wiremock.Get(wiremock.URLPathEqualTo("/basic")). 220 | WithBasicAuth("username", "password"). // same as: WithHeader("Authorization", wiremock.EqualTo("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")). 221 | WillReturnResponse(wiremock.NewResponse().WithStatus(http.StatusOK)) 222 | 223 | bearerTokenStub := wiremock.Get(wiremock.URLPathEqualTo("/bearer")). 224 | WithBearerToken(wiremock.Matching("^\\S+abc\\S+$")). // same as: WithHeader("Authorization", wiremock.Matching("^Bearer \\S+abc\\S+$")). 225 | WillReturnResponse(wiremock.NewResponse().WithStatus(http.StatusOK)) 226 | 227 | apiTokenStub := wiremock.Get(wiremock.URLPathEqualTo("/token")). 228 | WithAuthToken(wiremock.StartsWith("myToken123")). // same as: WithHeader("Authorization", wiremock.StartsWith("Token myToken123")). 229 | WillReturnResponse(wiremock.NewResponse().WithStatus(http.StatusOK)) 230 | 231 | digestAuthStub := wiremock.Get(wiremock.URLPathEqualTo("/digest")). 232 | WithDigestAuth(wiremock.Contains("realm")). // same as: WithHeader("Authorization", wiremock.StartsWith("Digest ").And(Contains("realm"))). 233 | WillReturnResponse(wiremock.NewResponse().WithStatus(http.StatusOK)) 234 | 235 | ``` 236 | 237 | ## License 238 | 239 | [MIT License](./LICENSE) 240 | 241 | ## See also 242 | 243 | - [Golang & WireMock Solutions page]( https://wiremock.org/docs/solutions/golang/) 244 | - [WireMock module for Testcontainers Go](https://wiremock.org/docs/solutions/testcontainers/) 245 | -------------------------------------------------------------------------------- /stub_rule.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "time" 7 | 8 | uuidPkg "github.com/google/uuid" 9 | ) 10 | 11 | const ScenarioStateStarted = "Started" 12 | const authorizationHeader = "Authorization" 13 | 14 | // StubRule is struct of http Request body to WireMock 15 | type StubRule struct { 16 | uuid string 17 | request *Request 18 | response ResponseInterface 19 | fixedDelayMilliseconds *int64 20 | priority *int64 21 | scenarioName *string 22 | requiredScenarioState *string 23 | newScenarioState *string 24 | postServeActions []WebhookInterface 25 | } 26 | 27 | // NewStubRule returns a new *StubRule. 28 | func NewStubRule(method string, urlMatcher URLMatcher) *StubRule { 29 | uuid, _ := uuidPkg.NewRandom() 30 | return &StubRule{ 31 | uuid: uuid.String(), 32 | request: NewRequest(method, urlMatcher), 33 | response: NewResponse(), 34 | } 35 | } 36 | 37 | // Request is getter for Request 38 | func (s *StubRule) Request() *Request { 39 | return s.request 40 | } 41 | 42 | // WithQueryParam adds query param and returns *StubRule 43 | func (s *StubRule) WithQueryParam(param string, matcher MatcherInterface) *StubRule { 44 | s.request.WithQueryParam(param, matcher) 45 | return s 46 | } 47 | 48 | // WithPathParam adds path param and returns *StubRule 49 | func (s *StubRule) WithPathParam(param string, matcher MatcherInterface) *StubRule { 50 | s.request.WithPathParam(param, matcher) 51 | return s 52 | } 53 | 54 | // WithPort adds port and returns *StubRule 55 | func (s *StubRule) WithPort(port int64) *StubRule { 56 | s.request.WithPort(port) 57 | return s 58 | } 59 | 60 | // WithScheme adds scheme and returns *StubRule 61 | func (s *StubRule) WithScheme(scheme string) *StubRule { 62 | s.request.WithScheme(scheme) 63 | return s 64 | } 65 | 66 | // WithHost adds host and returns *StubRule 67 | func (s *StubRule) WithHost(host BasicParamMatcher) *StubRule { 68 | s.request.WithHost(host) 69 | return s 70 | } 71 | 72 | // WithHeader adds header to Headers and returns *StubRule 73 | func (s *StubRule) WithHeader(header string, matcher MatcherInterface) *StubRule { 74 | s.request.WithHeader(header, matcher) 75 | return s 76 | } 77 | 78 | // WithCookie adds cookie and returns *StubRule 79 | func (s *StubRule) WithCookie(cookie string, matcher BasicParamMatcher) *StubRule { 80 | s.request.WithCookie(cookie, matcher) 81 | return s 82 | } 83 | 84 | // WithBodyPattern adds body pattern and returns *StubRule 85 | func (s *StubRule) WithBodyPattern(matcher BasicParamMatcher) *StubRule { 86 | s.request.WithBodyPattern(matcher) 87 | return s 88 | } 89 | 90 | // WithFormParameter adds form parameter and returns *StubRule 91 | func (s *StubRule) WithFormParameter(param string, matcher BasicParamMatcher) *StubRule { 92 | s.request.WithFormParameter(param, matcher) 93 | return s 94 | } 95 | 96 | // WithMultipartPattern adds multipart body pattern and returns *StubRule 97 | func (s *StubRule) WithMultipartPattern(pattern *MultipartPattern) *StubRule { 98 | s.request.WithMultipartPattern(pattern) 99 | return s 100 | } 101 | 102 | // WithAuthToken adds Authorization header with Token auth method *StubRule 103 | func (s *StubRule) WithAuthToken(tokenMatcher BasicParamMatcher) *StubRule { 104 | methodPrefix := "Token " 105 | m := addAuthMethodToMatcher(tokenMatcher, methodPrefix) 106 | s.WithHeader(authorizationHeader, StartsWith(methodPrefix).And(m)) 107 | return s 108 | } 109 | 110 | // WithBearerToken adds Authorization header with Bearer auth method *StubRule 111 | func (s *StubRule) WithBearerToken(tokenMatcher BasicParamMatcher) *StubRule { 112 | methodPrefix := "Bearer " 113 | m := addAuthMethodToMatcher(tokenMatcher, methodPrefix) 114 | s.WithHeader(authorizationHeader, StartsWith(methodPrefix).And(m)) 115 | return s 116 | } 117 | 118 | // WithDigestAuth adds Authorization header with Digest auth method *StubRule 119 | func (s *StubRule) WithDigestAuth(matcher BasicParamMatcher) *StubRule { 120 | methodPrefix := "Digest " 121 | m := addAuthMethodToMatcher(matcher, methodPrefix) 122 | s.WithHeader(authorizationHeader, StartsWith(methodPrefix).And(m)) 123 | return s 124 | } 125 | 126 | // Deprecated: Use WillReturnResponse(NewResponse().WithBody(body).WithHeaders(headers).WithStatus(status)) instead 127 | // WillReturn sets response and returns *StubRule 128 | func (s *StubRule) WillReturn(body string, headers map[string]string, status int64) *StubRule { 129 | s.response = NewResponse().WithBody(body).WithStatus(status).WithHeaders(headers) 130 | return s 131 | } 132 | 133 | // Deprecated: Use WillReturnResponse(NewResponse().WithBinaryBody(body).WithHeaders(headers).WithStatus(status)) instead 134 | // WillReturnBinary sets response with binary body and returns *StubRule 135 | func (s *StubRule) WillReturnBinary(body []byte, headers map[string]string, status int64) *StubRule { 136 | s.response = NewResponse().WithBinaryBody(body).WithStatus(status).WithHeaders(headers) 137 | return s 138 | } 139 | 140 | // Deprecated: Use WillReturnResponse(NewResponse().WithBodyFile(file).WithHeaders(headers).WithStatus(status)) instead 141 | // WillReturnFileContent sets response with some file content and returns *StubRule 142 | func (s *StubRule) WillReturnFileContent(bodyFileName string, headers map[string]string, status int64) *StubRule { 143 | s.response = NewResponse().WithBodyFile(bodyFileName).WithStatus(status).WithHeaders(headers) 144 | return s 145 | } 146 | 147 | // Deprecated: Use WillReturnResponse(NewResponse().WithJsonBody(json).WithHeaders(headers).WithStatus(status)) instead 148 | // WillReturnJSON sets response with json body and returns *StubRule 149 | func (s *StubRule) WillReturnJSON(json interface{}, headers map[string]string, status int64) *StubRule { 150 | s.response = NewResponse().WithJSONBody(json).WithStatus(status).WithHeaders(headers) 151 | return s 152 | } 153 | 154 | // Deprecated: Use WillReturnResponse(NewResponse().WithFixedDelay(time.Second)) instead 155 | // WithFixedDelayMilliseconds adds delay to response and returns *StubRule 156 | func (s *StubRule) WithFixedDelayMilliseconds(delay time.Duration) *StubRule { 157 | milliseconds := delay.Milliseconds() 158 | s.fixedDelayMilliseconds = &milliseconds 159 | return s 160 | } 161 | 162 | // WillReturnResponse sets response and returns *StubRule 163 | func (s *StubRule) WillReturnResponse(response ResponseInterface) *StubRule { 164 | s.response = response 165 | return s 166 | } 167 | 168 | // WithBasicAuth adds basic auth credentials 169 | func (s *StubRule) WithBasicAuth(username, password string) *StubRule { 170 | s.request.WithBasicAuth(username, password) 171 | return s 172 | } 173 | 174 | // AtPriority sets priority and returns *StubRule 175 | func (s *StubRule) AtPriority(priority int64) *StubRule { 176 | s.priority = &priority 177 | return s 178 | } 179 | 180 | // InScenario sets scenarioName and returns *StubRule 181 | func (s *StubRule) InScenario(scenarioName string) *StubRule { 182 | s.scenarioName = &scenarioName 183 | return s 184 | } 185 | 186 | // WhenScenarioStateIs sets requiredScenarioState and returns *StubRule 187 | func (s *StubRule) WhenScenarioStateIs(scenarioState string) *StubRule { 188 | s.requiredScenarioState = &scenarioState 189 | return s 190 | } 191 | 192 | // WillSetStateTo sets newScenarioState and returns *StubRule 193 | func (s *StubRule) WillSetStateTo(scenarioState string) *StubRule { 194 | s.newScenarioState = &scenarioState 195 | return s 196 | } 197 | 198 | // UUID is getter for uuid 199 | func (s *StubRule) UUID() string { 200 | return s.uuid 201 | } 202 | 203 | // Post returns *StubRule for POST method. 204 | func Post(urlMatchingPair URLMatcher) *StubRule { 205 | return NewStubRule(http.MethodPost, urlMatchingPair) 206 | } 207 | 208 | // Get returns *StubRule for GET method. 209 | func Get(urlMatchingPair URLMatcher) *StubRule { 210 | return NewStubRule(http.MethodGet, urlMatchingPair) 211 | } 212 | 213 | // Delete returns *StubRule for DELETE method. 214 | func Delete(urlMatchingPair URLMatcher) *StubRule { 215 | return NewStubRule(http.MethodDelete, urlMatchingPair) 216 | } 217 | 218 | // Put returns *StubRule for PUT method. 219 | func Put(urlMatchingPair URLMatcher) *StubRule { 220 | return NewStubRule(http.MethodPut, urlMatchingPair) 221 | } 222 | 223 | // Patch returns *StubRule for PATCH method. 224 | func Patch(urlMatchingPair URLMatcher) *StubRule { 225 | return NewStubRule(http.MethodPatch, urlMatchingPair) 226 | } 227 | 228 | func (s *StubRule) WithPostServeAction(extensionName string, webhook WebhookInterface) *StubRule { 229 | s.postServeActions = append(s.postServeActions, webhook.WithName(extensionName)) 230 | return s 231 | } 232 | 233 | // MarshalJSON makes json body for http Request 234 | func (s *StubRule) MarshalJSON() ([]byte, error) { 235 | jsonStubRule := struct { 236 | UUID string `json:"uuid,omitempty"` 237 | ID string `json:"id,omitempty"` 238 | Priority *int64 `json:"priority,omitempty"` 239 | ScenarioName *string `json:"scenarioName,omitempty"` 240 | RequiredScenarioScenarioState *string `json:"requiredScenarioState,omitempty"` 241 | NewScenarioState *string `json:"newScenarioState,omitempty"` 242 | Request *Request `json:"request"` 243 | Response map[string]interface{} `json:"response"` 244 | PostServeActions []WebhookInterface `json:"postServeActions,omitempty"` 245 | }{} 246 | 247 | jsonStubRule.Priority = s.priority 248 | jsonStubRule.ScenarioName = s.scenarioName 249 | jsonStubRule.RequiredScenarioScenarioState = s.requiredScenarioState 250 | jsonStubRule.NewScenarioState = s.newScenarioState 251 | jsonStubRule.Response = s.response.ParseResponse() 252 | jsonStubRule.PostServeActions = s.postServeActions 253 | 254 | if s.fixedDelayMilliseconds != nil { 255 | jsonStubRule.Response["fixedDelayMilliseconds"] = *s.fixedDelayMilliseconds 256 | } 257 | 258 | jsonStubRule.Request = s.request 259 | jsonStubRule.ID = s.uuid 260 | jsonStubRule.UUID = s.uuid 261 | 262 | return json.Marshal(jsonStubRule) 263 | } 264 | 265 | func addAuthMethodToMatcher(matcher BasicParamMatcher, methodPrefix string) BasicParamMatcher { 266 | switch m := matcher.(type) { 267 | case StringValueMatcher: 268 | return m.addPrefixToMatcher(methodPrefix) 269 | case LogicalMatcher: 270 | for i, operand := range m.operands { 271 | m.operands[i] = addAuthMethodToMatcher(operand, methodPrefix) 272 | } 273 | return m 274 | default: 275 | return matcher 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package wiremock_test 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "reflect" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/google/uuid" 11 | tc "github.com/testcontainers/testcontainers-go" 12 | "github.com/testcontainers/testcontainers-go/wait" 13 | 14 | "github.com/wiremock/go-wiremock" 15 | ) 16 | 17 | type WiremockTestService struct { 18 | container tc.Container 19 | client *wiremock.Client 20 | baseURL string 21 | } 22 | 23 | func getWiremockTestService(ctx context.Context, t *testing.T) *WiremockTestService { 24 | req := tc.ContainerRequest{ 25 | Name: "go-wiremock", 26 | Image: "wiremock/wiremock:latest", 27 | ExposedPorts: []string{"8080/tcp"}, 28 | Cmd: []string{"--verbose"}, 29 | WaitingFor: wait.ForHealthCheck(), 30 | } 31 | 32 | c, err := tc.GenericContainer(ctx, tc.GenericContainerRequest{ 33 | ContainerRequest: req, 34 | Started: true, 35 | Reuse: true, 36 | }) 37 | requireNoError(t, err) 38 | 39 | baseURL, err := c.PortEndpoint(ctx, "8080/tcp", "http") 40 | requireNoError(t, err) 41 | 42 | client := wiremock.NewClient(baseURL) 43 | 44 | return &WiremockTestService{ 45 | container: c, 46 | client: client, 47 | baseURL: baseURL, 48 | } 49 | } 50 | 51 | // Reset is a shortcut for client.Reset 52 | func (s *WiremockTestService) Reset() error { 53 | return s.client.Reset() 54 | } 55 | 56 | func TestClient_GetAllRequests(t *testing.T) { 57 | ctx := context.Background() 58 | svc := getWiremockTestService(ctx, t) 59 | 60 | t.Run("empty request journal", func(t *testing.T) { 61 | err := svc.Reset() 62 | requireNoError(t, err) 63 | 64 | events, err := svc.client.GetAllRequests() 65 | requireNoError(t, err) 66 | 67 | assertEqual(t, 0, events.Meta.Total) 68 | assertEqual(t, 0, len(events.Requests)) 69 | }) 70 | 71 | t.Run("with requests", func(t *testing.T) { 72 | err := svc.Reset() 73 | requireNoError(t, err) 74 | 75 | stub := wiremock.NewStubRule("GET", wiremock.URLMatching("/test")).WillReturnResponse(wiremock.OK()) 76 | err = svc.client.StubFor(stub) 77 | requireNoError(t, err) 78 | 79 | _, err = http.Get(svc.baseURL + "/test") 80 | requireNoError(t, err) 81 | 82 | _, err = http.Get(svc.baseURL + "/not-a-stub?param=1234") 83 | requireNoError(t, err) 84 | 85 | events, err := svc.client.GetAllRequests() 86 | requireNoError(t, err) 87 | 88 | assertEqual(t, 2, events.Meta.Total) 89 | assertEqual(t, 2, len(events.Requests)) 90 | assertEqual(t, "/not-a-stub?param=1234", events.Requests[0].Request.URL) 91 | assertEqual(t, "/test", events.Requests[1].Request.URL) 92 | }) 93 | } 94 | 95 | func TestClient_GetRequestsByID(t *testing.T) { 96 | ctx := context.Background() 97 | svc := getWiremockTestService(ctx, t) 98 | 99 | t.Run("invalid request id", func(t *testing.T) { 100 | err := svc.Reset() 101 | requireNoError(t, err) 102 | 103 | id := uuid.New() 104 | 105 | request, err := svc.client.GetRequestByID(id.String()) 106 | if err == nil { 107 | t.Fatal("expected error, got none") 108 | } 109 | if !strings.Contains(err.Error(), "bad response status: 404") { 110 | t.Errorf("expected error message to contain 'bad response status: 404', got %s", err.Error()) 111 | } 112 | 113 | assertNil(t, request) 114 | }) 115 | 116 | t.Run("valid request id", func(t *testing.T) { 117 | err := svc.Reset() 118 | requireNoError(t, err) 119 | 120 | _, err = http.Get(svc.baseURL + "/test-1") 121 | requireNoError(t, err) 122 | 123 | events, err := svc.client.GetAllRequests() 124 | requireNoError(t, err) 125 | 126 | // Add another request to the journal to ensure we're really getting the one we asked for 127 | _, err = http.Get(svc.baseURL + "/test-2") 128 | requireNoError(t, err) 129 | 130 | reqID := events.Requests[0].ID 131 | 132 | req, err := svc.client.GetRequestByID(reqID) 133 | requireNoError(t, err) 134 | 135 | assertEqual(t, "/test-1", req.Request.URL) 136 | }) 137 | } 138 | 139 | func TestClient_FindRequestsByCriteria(t *testing.T) { 140 | ctx := context.Background() 141 | svc := getWiremockTestService(ctx, t) 142 | 143 | t.Run("no requests to find", func(t *testing.T) { 144 | err := svc.Reset() 145 | requireNoError(t, err) 146 | 147 | stub := wiremock.NewStubRule("GET", wiremock.URLMatching("/test")).WillReturnResponse(wiremock.OK()) 148 | err = svc.client.StubFor(stub) 149 | requireNoError(t, err) 150 | 151 | _, err = http.Get(svc.baseURL + "/other") 152 | requireNoError(t, err) 153 | 154 | // Find requests for /test 155 | resp, err := svc.client.FindRequestsByCriteria(stub.Request()) 156 | requireNoError(t, err) 157 | assertEqual(t, 0, len(resp.Requests)) 158 | }) 159 | 160 | t.Run("with requests to find", func(t *testing.T) { 161 | err := svc.Reset() 162 | requireNoError(t, err) 163 | 164 | stub := wiremock.NewStubRule("GET", wiremock.URLMatching("/test")).WillReturnResponse(wiremock.OK()) 165 | err = svc.client.StubFor(stub) 166 | requireNoError(t, err) 167 | 168 | _, err = http.Get(svc.baseURL + "/test") 169 | requireNoError(t, err) 170 | 171 | _, err = http.Get(svc.baseURL + "/test") 172 | requireNoError(t, err) 173 | 174 | _, err = http.Get(svc.baseURL + "/other") 175 | requireNoError(t, err) 176 | 177 | // Find requests for /test 178 | resp, err := svc.client.FindRequestsByCriteria(stub.Request()) 179 | requireNoError(t, err) 180 | 181 | assertEqual(t, 2, len(resp.Requests)) 182 | for _, r := range resp.Requests { 183 | assertEqual(t, "/test", r.URL) 184 | } 185 | 186 | events, err := svc.client.GetAllRequests() 187 | requireNoError(t, err) 188 | 189 | // All requests should be in the events 190 | assertEqual(t, 3, len(events.Requests)) 191 | }) 192 | } 193 | 194 | func TestClient_FindUnmatchedRequests(t *testing.T) { 195 | ctx := context.Background() 196 | svc := getWiremockTestService(ctx, t) 197 | 198 | t.Run("no requests to find", func(t *testing.T) { 199 | err := svc.Reset() 200 | requireNoError(t, err) 201 | 202 | stub := wiremock.NewStubRule("GET", wiremock.URLMatching("/test")).WillReturnResponse(wiremock.OK()) 203 | err = svc.client.StubFor(stub) 204 | requireNoError(t, err) 205 | 206 | _, err = http.Get(svc.baseURL + "/test") 207 | requireNoError(t, err) 208 | 209 | resp, err := svc.client.FindUnmatchedRequests() 210 | requireNoError(t, err) 211 | assertEqual(t, 0, len(resp.Requests)) 212 | }) 213 | 214 | t.Run("with requests to find", func(t *testing.T) { 215 | err := svc.Reset() 216 | requireNoError(t, err) 217 | 218 | stub := wiremock.NewStubRule("GET", wiremock.URLMatching("/test")).WillReturnResponse(wiremock.OK()) 219 | err = svc.client.StubFor(stub) 220 | requireNoError(t, err) 221 | 222 | _, err = http.Get(svc.baseURL + "/test") 223 | requireNoError(t, err) 224 | 225 | _, err = http.Get(svc.baseURL + "/unmatched") 226 | requireNoError(t, err) 227 | 228 | _, err = http.Get(svc.baseURL + "/unmatched") 229 | requireNoError(t, err) 230 | 231 | // Find requests for /test 232 | resp, err := svc.client.FindUnmatchedRequests() 233 | requireNoError(t, err) 234 | 235 | assertEqual(t, 2, len(resp.Requests)) 236 | for _, r := range resp.Requests { 237 | assertEqual(t, "/unmatched", r.URL) 238 | } 239 | }) 240 | } 241 | 242 | func TestClient_DeleteAllRequests(t *testing.T) { 243 | ctx := context.Background() 244 | svc := getWiremockTestService(ctx, t) 245 | 246 | err := svc.Reset() 247 | requireNoError(t, err) 248 | 249 | _, err = http.Get(svc.baseURL + "/test") 250 | requireNoError(t, err) 251 | 252 | _, err = http.Get(svc.baseURL + "/test2") 253 | requireNoError(t, err) 254 | 255 | events, err := svc.client.GetAllRequests() 256 | requireNoError(t, err) 257 | 258 | assertEqual(t, 2, events.Meta.Total) 259 | 260 | err = svc.client.DeleteAllRequests() 261 | requireNoError(t, err) 262 | 263 | events, err = svc.client.GetAllRequests() 264 | requireNoError(t, err) 265 | 266 | assertEqual(t, 0, events.Meta.Total) 267 | } 268 | 269 | func TestClient_DeleteRequestByID(t *testing.T) { 270 | ctx := context.Background() 271 | svc := getWiremockTestService(ctx, t) 272 | 273 | t.Run("invalid request id", func(t *testing.T) { 274 | err := svc.Reset() 275 | requireNoError(t, err) 276 | 277 | id := uuid.New() 278 | 279 | err = svc.client.DeleteRequestByID(id.String()) 280 | // Deleting a non-existing request does nothing and returns a 200. 281 | requireNoError(t, err) 282 | }) 283 | 284 | t.Run("valid request id", func(t *testing.T) { 285 | err := svc.Reset() 286 | requireNoError(t, err) 287 | 288 | _, err = http.Get(svc.baseURL + "/test-1") 289 | requireNoError(t, err) 290 | 291 | events, err := svc.client.GetAllRequests() 292 | requireNoError(t, err) 293 | 294 | // Add another request to the journal, it should still be there after the deletion 295 | _, err = http.Get(svc.baseURL + "/test-2") 296 | requireNoError(t, err) 297 | 298 | reqID := events.Requests[0].ID 299 | 300 | // Delete request for /test-1 301 | err = svc.client.DeleteRequestByID(reqID) 302 | requireNoError(t, err) 303 | 304 | events, err = svc.client.GetAllRequests() 305 | requireNoError(t, err) 306 | 307 | assertEqual(t, 1, len(events.Requests)) 308 | assertEqual(t, "/test-2", events.Requests[0].Request.URL) 309 | }) 310 | } 311 | 312 | func TestClient_DeleteRequestsByCriteria(t *testing.T) { 313 | ctx := context.Background() 314 | svc := getWiremockTestService(ctx, t) 315 | 316 | t.Run("no requests to delete", func(t *testing.T) { 317 | err := svc.Reset() 318 | requireNoError(t, err) 319 | 320 | stub := wiremock.NewStubRule("GET", wiremock.URLMatching("/test")).WillReturnResponse(wiremock.OK()) 321 | err = svc.client.StubFor(stub) 322 | requireNoError(t, err) 323 | 324 | _, err = http.Get(svc.baseURL + "/other") 325 | requireNoError(t, err) 326 | 327 | // Delete requests for /test 328 | resp, err := svc.client.DeleteRequestsByCriteria(stub.Request()) 329 | requireNoError(t, err) 330 | assertEqual(t, 0, len(resp.Requests)) 331 | 332 | events, err := svc.client.GetAllRequests() 333 | requireNoError(t, err) 334 | 335 | // The other request should still be present 336 | assertEqual(t, 1, len(events.Requests)) 337 | assertEqual(t, "/other", events.Requests[0].Request.URL) 338 | }) 339 | 340 | t.Run("with requests to delete", func(t *testing.T) { 341 | err := svc.Reset() 342 | requireNoError(t, err) 343 | 344 | stub := wiremock.NewStubRule("GET", wiremock.URLMatching("/test")).WillReturnResponse(wiremock.OK()) 345 | err = svc.client.StubFor(stub) 346 | requireNoError(t, err) 347 | 348 | _, err = http.Get(svc.baseURL + "/test") 349 | requireNoError(t, err) 350 | 351 | _, err = http.Get(svc.baseURL + "/test") 352 | requireNoError(t, err) 353 | 354 | _, err = http.Get(svc.baseURL + "/other") 355 | requireNoError(t, err) 356 | 357 | // Delete requests for /test 358 | resp, err := svc.client.DeleteRequestsByCriteria(stub.Request()) 359 | requireNoError(t, err) 360 | 361 | assertEqual(t, 2, len(resp.Requests)) 362 | for _, evt := range resp.Requests { 363 | assertEqual(t, "/test", evt.Request.URL) 364 | } 365 | 366 | events, err := svc.client.GetAllRequests() 367 | requireNoError(t, err) 368 | 369 | // The other request should be the only one remaining 370 | assertEqual(t, 1, len(events.Requests)) 371 | assertEqual(t, "/other", events.Requests[0].Request.URL) 372 | }) 373 | } 374 | 375 | func requireNoError(t *testing.T, err error) { 376 | if err != nil { 377 | t.Fatal(err) 378 | } 379 | } 380 | 381 | func assertNil[T any](t *testing.T, value *T) { 382 | if value != nil { 383 | t.Errorf("expected nil, got %v", value) 384 | } 385 | } 386 | 387 | func assertEqual[T any](t *testing.T, expected, actual T) { 388 | if !reflect.DeepEqual(expected, actual) { 389 | t.Errorf("expected %T(%v), got %T(%v)", expected, expected, actual, actual) 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "strings" 10 | 11 | "github.com/wiremock/go-wiremock/journal" 12 | ) 13 | 14 | const ( 15 | wiremockAdminURN = "__admin" 16 | wiremockAdminMappingsURN = "__admin/mappings" 17 | wiremockAdminRequestsURN = "__admin/requests" 18 | ) 19 | 20 | // A Client implements requests to the wiremock server. 21 | type Client struct { 22 | url string 23 | } 24 | 25 | // NewClient returns *Client. 26 | func NewClient(url string) *Client { 27 | return &Client{url: url} 28 | } 29 | 30 | // StubFor creates a new stub mapping. 31 | func (c *Client) StubFor(stubRule *StubRule) error { 32 | requestBody, err := stubRule.MarshalJSON() 33 | if err != nil { 34 | return fmt.Errorf("build stub request error: %w", err) 35 | } 36 | 37 | res, err := http.Post(fmt.Sprintf("%s/%s", c.url, wiremockAdminMappingsURN), "application/json", bytes.NewBuffer(requestBody)) 38 | if err != nil { 39 | return fmt.Errorf("stub request error: %w", err) 40 | } 41 | defer res.Body.Close() 42 | 43 | if res.StatusCode != http.StatusCreated { 44 | bodyBytes, err := io.ReadAll(res.Body) 45 | if err != nil { 46 | return fmt.Errorf("read response error: %w", err) 47 | } 48 | 49 | return fmt.Errorf("bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 50 | } 51 | 52 | return nil 53 | } 54 | 55 | // Clear deletes all stub mappings. 56 | func (c *Client) Clear() error { 57 | req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/%s", c.url, wiremockAdminMappingsURN), nil) 58 | if err != nil { 59 | return fmt.Errorf("build cleare Request error: %w", err) 60 | } 61 | 62 | res, err := (&http.Client{}).Do(req) 63 | if err != nil { 64 | return fmt.Errorf("clear Request error: %w", err) 65 | } 66 | defer res.Body.Close() 67 | 68 | if res.StatusCode != http.StatusOK { 69 | return fmt.Errorf("bad response status: %d", res.StatusCode) 70 | } 71 | 72 | return nil 73 | } 74 | 75 | // Reset restores stub mappings to the defaults defined back in the backing store. 76 | func (c *Client) Reset() error { 77 | res, err := http.Post(fmt.Sprintf("%s/%s/reset", c.url, wiremockAdminMappingsURN), "application/json", nil) 78 | if err != nil { 79 | return fmt.Errorf("reset Request error: %w", err) 80 | } 81 | defer res.Body.Close() 82 | 83 | if res.StatusCode != http.StatusOK { 84 | bodyBytes, err := io.ReadAll(res.Body) 85 | if err != nil { 86 | return fmt.Errorf("read response error: %w", err) 87 | } 88 | 89 | return fmt.Errorf("bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 90 | } 91 | 92 | return nil 93 | } 94 | 95 | // ResetAllScenarios resets back to start of the state of all configured scenarios. 96 | func (c *Client) ResetAllScenarios() error { 97 | res, err := http.Post(fmt.Sprintf("%s/%s/scenarios/reset", c.url, wiremockAdminURN), "application/json", nil) 98 | if err != nil { 99 | return fmt.Errorf("reset all scenarios Request error: %w", err) 100 | } 101 | defer res.Body.Close() 102 | 103 | if res.StatusCode != http.StatusOK { 104 | bodyBytes, err := io.ReadAll(res.Body) 105 | if err != nil { 106 | return fmt.Errorf("read response error: %w", err) 107 | } 108 | 109 | return fmt.Errorf("bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 110 | } 111 | 112 | return nil 113 | } 114 | 115 | // GetCountRequests gives count requests by criteria. 116 | func (c *Client) GetCountRequests(r *Request) (int64, error) { 117 | requestBody, err := r.MarshalJSON() 118 | if err != nil { 119 | return 0, fmt.Errorf("get count requests: build error: %w", err) 120 | } 121 | 122 | res, err := http.Post(fmt.Sprintf("%s/%s/requests/count", c.url, wiremockAdminURN), "application/json", bytes.NewBuffer(requestBody)) 123 | if err != nil { 124 | return 0, fmt.Errorf("get count requests: %w", err) 125 | } 126 | defer res.Body.Close() 127 | 128 | bodyBytes, err := io.ReadAll(res.Body) 129 | if err != nil { 130 | return 0, fmt.Errorf("get count requests: read response error: %w", err) 131 | } 132 | 133 | if res.StatusCode != http.StatusOK { 134 | return 0, fmt.Errorf("get count requests: bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 135 | } 136 | 137 | var countRequestsResponse struct { 138 | Count int64 `json:"count"` 139 | } 140 | 141 | err = json.Unmarshal(bodyBytes, &countRequestsResponse) 142 | if err != nil { 143 | return 0, fmt.Errorf("get count requests: read json error: %w", err) 144 | } 145 | 146 | return countRequestsResponse.Count, nil 147 | } 148 | 149 | // Verify checks count of request sent. 150 | func (c *Client) Verify(r *Request, expectedCount int64) (bool, error) { 151 | actualCount, err := c.GetCountRequests(r) 152 | if err != nil { 153 | return false, err 154 | } 155 | 156 | return actualCount == expectedCount, nil 157 | } 158 | 159 | // GetAllRequests returns all requests logged in the journal. 160 | func (c *Client) GetAllRequests() (*journal.GetAllRequestsResponse, error) { 161 | res, err := http.Get(fmt.Sprintf("%s/%s", c.url, wiremockAdminRequestsURN)) 162 | if err != nil { 163 | return nil, fmt.Errorf("get all requests: %w", err) 164 | } 165 | defer res.Body.Close() 166 | 167 | bodyBytes, err := io.ReadAll(res.Body) 168 | if err != nil { 169 | return nil, fmt.Errorf("get all requests: read response error: %w", err) 170 | } 171 | 172 | if res.StatusCode != http.StatusOK { 173 | return nil, fmt.Errorf("get all requests: bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 174 | } 175 | 176 | var response journal.GetAllRequestsResponse 177 | err = json.Unmarshal(bodyBytes, &response) 178 | if err != nil { 179 | return nil, fmt.Errorf("get all requests: error unmarshalling response: %w", err) 180 | } 181 | return &response, nil 182 | } 183 | 184 | // GetRequestByID retrieves a single request from the journal, by its ID. 185 | func (c *Client) GetRequestByID(requestID string) (*journal.GetRequestResponse, error) { 186 | res, err := http.Get(fmt.Sprintf("%s/%s/%s", c.url, wiremockAdminRequestsURN, requestID)) 187 | if err != nil { 188 | return nil, fmt.Errorf("get request by id: build request error: %w", err) 189 | } 190 | defer res.Body.Close() 191 | 192 | bodyBytes, err := io.ReadAll(res.Body) 193 | if err != nil { 194 | return nil, fmt.Errorf("get request by id: read response error: %w", err) 195 | } 196 | 197 | if res.StatusCode != http.StatusOK { 198 | return nil, fmt.Errorf("get request by id: bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 199 | } 200 | 201 | var response journal.GetRequestResponse 202 | err = json.Unmarshal(bodyBytes, &response) 203 | if err != nil { 204 | return nil, fmt.Errorf("get request by id: error unmarshalling response: %w", err) 205 | } 206 | return &response, nil 207 | } 208 | 209 | // FindRequestsByCriteria returns all requests in the journal matching the criteria. 210 | func (c *Client) FindRequestsByCriteria(r *Request) (*journal.FindRequestsByCriteriaResponse, error) { 211 | requestBody, err := r.MarshalJSON() 212 | if err != nil { 213 | return nil, fmt.Errorf("find requests by criteria: build error: %w", err) 214 | } 215 | 216 | res, err := http.Post(fmt.Sprintf("%s/%s/find", c.url, wiremockAdminRequestsURN), "application/json", bytes.NewBuffer(requestBody)) 217 | if err != nil { 218 | return nil, fmt.Errorf("find requests by criteria: request error: %w", err) 219 | } 220 | defer res.Body.Close() 221 | 222 | bodyBytes, err := io.ReadAll(res.Body) 223 | if err != nil { 224 | return nil, fmt.Errorf("find requests by criteria: read response error: %w", err) 225 | } 226 | 227 | if res.StatusCode != http.StatusOK { 228 | return nil, fmt.Errorf("find requests by criteria: bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 229 | } 230 | 231 | var requests journal.FindRequestsByCriteriaResponse 232 | err = json.Unmarshal(bodyBytes, &requests) 233 | if err != nil { 234 | return nil, fmt.Errorf("find requests by criteria: read json error: %w", err) 235 | } 236 | return &requests, nil 237 | } 238 | 239 | // FindUnmatchedRequests returns all requests in the journal matching the criteria. 240 | func (c *Client) FindUnmatchedRequests() (*journal.FindUnmatchedRequestsResponse, error) { 241 | res, err := http.Get(fmt.Sprintf("%s/%s/unmatched", c.url, wiremockAdminRequestsURN)) 242 | if err != nil { 243 | return nil, fmt.Errorf("find unmatched requests: request error: %w", err) 244 | } 245 | defer res.Body.Close() 246 | 247 | bodyBytes, err := io.ReadAll(res.Body) 248 | if err != nil { 249 | return nil, fmt.Errorf("find unmatched requests: read response error: %w", err) 250 | } 251 | 252 | if res.StatusCode != http.StatusOK { 253 | return nil, fmt.Errorf("find unmatched requests: bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 254 | } 255 | 256 | var requests journal.FindUnmatchedRequestsResponse 257 | err = json.Unmarshal(bodyBytes, &requests) 258 | if err != nil { 259 | return nil, fmt.Errorf("find unmatched requests: read json error: %w", err) 260 | } 261 | return &requests, nil 262 | } 263 | 264 | // DeleteAllRequests deletes all the requests in the journal. 265 | func (c *Client) DeleteAllRequests() error { 266 | req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/%s", c.url, wiremockAdminRequestsURN), nil) 267 | if err != nil { 268 | return fmt.Errorf("delete all requests: build error: %w", err) 269 | } 270 | res, err := (&http.Client{}).Do(req) 271 | 272 | if err != nil { 273 | return fmt.Errorf("delete all requests: request error: %w", err) 274 | } 275 | defer res.Body.Close() 276 | 277 | if res.StatusCode != http.StatusOK { 278 | bodyBytes, err := io.ReadAll(res.Body) 279 | if err != nil { 280 | return fmt.Errorf("read response error: %w", err) 281 | } 282 | return fmt.Errorf("bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 283 | } 284 | return nil 285 | } 286 | 287 | // DeleteRequestByID deletes a single request from the journal, by its ID. 288 | func (c *Client) DeleteRequestByID(requestID string) error { 289 | req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/%s/%s", c.url, wiremockAdminRequestsURN, requestID), nil) 290 | if err != nil { 291 | return fmt.Errorf("delete request by id: build request error: %w", err) 292 | } 293 | 294 | res, err := (&http.Client{}).Do(req) 295 | if err != nil { 296 | return fmt.Errorf("delete request by id: request error: %w", err) 297 | } 298 | defer res.Body.Close() 299 | 300 | if res.StatusCode != http.StatusOK { 301 | bodyBytes, err := io.ReadAll(res.Body) 302 | if err != nil { 303 | return fmt.Errorf("delete request by id: read response error: %w", err) 304 | } 305 | return fmt.Errorf("delete request by id: bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 306 | } 307 | return nil 308 | } 309 | 310 | // DeleteRequestsByCriteria deletes all requests in the journal matching the criteria. 311 | func (c *Client) DeleteRequestsByCriteria(r *Request) (*journal.DeleteRequestByCriteriaResponse, error) { 312 | requestBody, err := r.MarshalJSON() 313 | if err != nil { 314 | return nil, fmt.Errorf("delete requests by criteria: build error: %w", err) 315 | } 316 | 317 | res, err := http.Post(fmt.Sprintf("%s/%s/remove", c.url, wiremockAdminRequestsURN), "application/json", bytes.NewBuffer(requestBody)) 318 | if err != nil { 319 | return nil, fmt.Errorf("delete requests by criteria: request error: %w", err) 320 | } 321 | defer res.Body.Close() 322 | 323 | bodyBytes, err := io.ReadAll(res.Body) 324 | if err != nil { 325 | return nil, fmt.Errorf("delete requests by criteria: read response error: %w", err) 326 | } 327 | 328 | if res.StatusCode != http.StatusOK { 329 | return nil, fmt.Errorf("delete requests by criteria: bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 330 | } 331 | 332 | var requests journal.DeleteRequestByCriteriaResponse 333 | err = json.Unmarshal(bodyBytes, &requests) 334 | if err != nil { 335 | return nil, fmt.Errorf("delete requests by criteria: error unmarshalling response: %w", err) 336 | } 337 | return &requests, nil 338 | } 339 | 340 | // DeleteStubByID deletes stub by id. 341 | func (c *Client) DeleteStubByID(id string) error { 342 | req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/%s/%s", c.url, wiremockAdminMappingsURN, id), nil) 343 | if err != nil { 344 | return fmt.Errorf("delete stub by id: build request error: %w", err) 345 | } 346 | 347 | res, err := (&http.Client{}).Do(req) 348 | if err != nil { 349 | return fmt.Errorf("delete stub by id: request error: %w", err) 350 | } 351 | 352 | defer res.Body.Close() 353 | 354 | if res.StatusCode != http.StatusOK { 355 | bodyBytes, err := io.ReadAll(res.Body) 356 | if err != nil { 357 | return fmt.Errorf("read response error: %w", err) 358 | } 359 | 360 | return fmt.Errorf("bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 361 | } 362 | 363 | return nil 364 | } 365 | 366 | // DeleteStub deletes stub mapping. 367 | func (c *Client) DeleteStub(s *StubRule) error { 368 | return c.DeleteStubByID(s.UUID()) 369 | } 370 | 371 | // StartRecording starts a recording. 372 | func (c *Client) StartRecording(targetBaseUrl string) error { 373 | requestBody := fmt.Sprintf(`{"targetBaseUrl":"%s"}`, targetBaseUrl) 374 | res, err := http.Post( 375 | fmt.Sprintf("%s/%s/recordings/start", c.url, wiremockAdminURN), 376 | "application/json", 377 | strings.NewReader(requestBody), 378 | ) 379 | if err != nil { 380 | return fmt.Errorf("start recording error: %w", err) 381 | } 382 | 383 | defer res.Body.Close() 384 | if res.StatusCode != http.StatusOK { 385 | bodyBytes, err := io.ReadAll(res.Body) 386 | if err != nil { 387 | return fmt.Errorf("read response error: %w", err) 388 | } 389 | 390 | return fmt.Errorf("bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 391 | } 392 | 393 | return err 394 | } 395 | 396 | // StopRecording stops a recording. 397 | func (c *Client) StopRecording() error { 398 | res, err := http.Post( 399 | fmt.Sprintf("%s/%s/recordings/stop", c.url, wiremockAdminURN), 400 | "application/json", 401 | nil, 402 | ) 403 | if err != nil { 404 | return fmt.Errorf("stop recording error: %w", err) 405 | } 406 | 407 | defer res.Body.Close() 408 | if res.StatusCode != http.StatusOK { 409 | bodyBytes, err := io.ReadAll(res.Body) 410 | if err != nil { 411 | return fmt.Errorf("read response error: %w", err) 412 | } 413 | 414 | return fmt.Errorf("bad response status: %d, response: %s", res.StatusCode, string(bodyBytes)) 415 | } 416 | 417 | return nil 418 | } 419 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 2 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= 4 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 5 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 6 | github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= 7 | github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= 8 | github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= 9 | github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= 10 | github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= 11 | github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 12 | github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= 13 | github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= 14 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 15 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 16 | github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= 17 | github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 18 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 19 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 21 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= 23 | github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 24 | github.com/docker/docker v25.0.2+incompatible h1:/OaKeauroa10K4Nqavw4zlhcDq/WBcPMc5DbjOGgozY= 25 | github.com/docker/docker v25.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 26 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 27 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 28 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 29 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 30 | github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= 31 | github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 32 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 33 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 34 | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 35 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 36 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 37 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 38 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 39 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 40 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 41 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 42 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 43 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 44 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 45 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 46 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 47 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 48 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 49 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 50 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 51 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= 52 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 53 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 54 | github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= 55 | github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 56 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 57 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 58 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 59 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 60 | github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= 61 | github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= 62 | github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= 63 | github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= 64 | github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= 65 | github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= 66 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 67 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 68 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 69 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 70 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 71 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 72 | github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= 73 | github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= 74 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 75 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 76 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 77 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 78 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 79 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 80 | github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= 81 | github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= 82 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 83 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 84 | github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= 85 | github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= 86 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 87 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 88 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 89 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 90 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 91 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 92 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 93 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 94 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 95 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 96 | github.com/testcontainers/testcontainers-go v0.28.0 h1:1HLm9qm+J5VikzFDYhOd+Zw12NtOl+8drH2E8nTY1r8= 97 | github.com/testcontainers/testcontainers-go v0.28.0/go.mod h1:COlDpUXbwW3owtpMkEB1zo9gwb1CoKVKlyrVPejF4AU= 98 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 99 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 100 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 101 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 102 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 103 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 104 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 105 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 106 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= 107 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= 108 | go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= 109 | go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= 110 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= 111 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= 112 | go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= 113 | go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= 114 | go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= 115 | go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= 116 | go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= 117 | go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= 118 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 119 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 120 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 121 | golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= 122 | golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= 123 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 124 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 125 | golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= 126 | golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 127 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 128 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 129 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 130 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 131 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 132 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 133 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 134 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 135 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 136 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 137 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 138 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 139 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 140 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 142 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 143 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 144 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 145 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 146 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 147 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 148 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 149 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 150 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 151 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 152 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 153 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 154 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 155 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 156 | golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= 157 | golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= 158 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 159 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 160 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 161 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 162 | google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= 163 | google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= 164 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= 165 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= 166 | google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= 167 | google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= 168 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 169 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 170 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 171 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 172 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 173 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 174 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 175 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 176 | gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= 177 | --------------------------------------------------------------------------------