• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pact-foundation / pact-go / 13706004865

06 Mar 2025 06:51PM UTC coverage: 28.238% (+0.03%) from 28.209%
13706004865

push

github

YOU54F
fix: strip v prefix from pact-go verifier version

0 of 1 new or added line in 1 file covered. (0.0%)

140 existing lines in 4 files now uncovered.

1805 of 6392 relevant lines covered (28.24%)

8.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.73
/consumer/http.go
1
// package consumer contains the main Pact DSL used in the Consumer
2
// collaboration test cases, and Provider contract test verification.
3
package consumer
4

5
// TODO: setup a proper state machine to prevent actions -> use type state fluent pattern
6
// Current issues
7
// 1. Setup needs to be initialised to get a port -> should be resolved by creating the server at the point of verification
8
// 2. Ensure that interactions are properly cleared
9
// 3. Need to ensure only v2 or v3 matchers are added
10

11
import (
12
        "crypto/tls"
13
        "errors"
14
        "fmt"
15
        "log"
16
        "os"
17
        "path/filepath"
18
        "runtime"
19
        "strings"
20
        "testing"
21
        "time"
22
        "unicode"
23
        "unicode/utf8"
24

25
        "github.com/pact-foundation/pact-go/v2/command"
26
        "github.com/pact-foundation/pact-go/v2/internal/native"
27
        logging "github.com/pact-foundation/pact-go/v2/log"
28
        "github.com/pact-foundation/pact-go/v2/models"
29
        "github.com/pact-foundation/pact-go/v2/utils"
30
)
31

32
// MockHTTPProviderConfig provides the configuration options for an HTTP mock server
33
// consumer test.
34
type MockHTTPProviderConfig struct {
35
        // Consumer is the name of the Consumer/Client.
36
        Consumer string
37

38
        // Provider is the name of the Providing service.
39
        Provider string
40

41
        // Location of Pact external service invocation output logging.
42
        // Defaults to `<cwd>/logs`.
43
        LogDir string
44

45
        // Pact files will be saved in this folder.
46
        // Defaults to `<cwd>/pacts`.
47
        PactDir string
48

49
        // Host is the address of the Mock and Verification Service runs on
50
        // Examples include 'localhost', '127.0.0.1', '[::1]'
51
        // Defaults to 'localhost'
52
        Host string
53

54
        // Network is the network of the Mock and Verification Service
55
        // Examples include 'tcp', 'tcp4', 'tcp6'
56
        // Defaults to 'tcp'
57
        // Network string
58

59
        // Ports MockServer can be deployed to, can be CSV or Range with a dash
60
        // Example "1234", "12324,5667", "1234-5667"
61
        AllowedMockServerPorts string
62

63
        // Port the mock service should run on. Leave blank to have one assigned
64
        // automatically by the OS.
65
        // Use AllowedMockServerPorts to constrain the assigned range.
66
        Port int
67

68
        // ClientTimeout specifies how long to wait for Pact CLI to start
69
        // Can be increased to reduce likelihood of intermittent failure
70
        // Defaults to 10s
71
        ClientTimeout time.Duration
72

73
        // TLS enables a mock service behind a self-signed certificate
74
        // TODO: document and test this
75
        TLS bool
76
}
77

78
// httpMockProvider is the entrypoint for http consumer tests
79
// This object is not thread safe
80
type httpMockProvider struct {
81
        specificationVersion models.SpecificationVersion
82
        config               MockHTTPProviderConfig
83
        mockserver           *native.MockServer
84
}
85

86
// MockServerConfig stores the address configuration details of the server for the current executing test
87
// This is most useful for the use of OS assigned, dynamic ports and parallel tests
88
type MockServerConfig struct {
89
        Port      int
90
        Host      string
91
        TLSConfig *tls.Config
92
}
93

94
// configure validates the configuration for the consumer test
95
func (p *httpMockProvider) configure() error {
18✔
96
        log.Println("[DEBUG] pact setup")
18✔
97
        dir, _ := os.Getwd()
18✔
98

18✔
99
        if p.config.Host == "" {
24✔
100
                p.config.Host = "127.0.0.1"
6✔
101
        }
6✔
102

103
        if p.config.LogDir == "" {
24✔
104
                p.config.LogDir = filepath.Join(dir, "logs")
6✔
105
        }
6✔
106

107
        if p.config.PactDir == "" {
24✔
108
                p.config.PactDir = filepath.Join(dir, "pacts")
6✔
109
        }
6✔
110

111
        if p.config.ClientTimeout == 0 {
24✔
112
                p.config.ClientTimeout = 10 * time.Second
6✔
113
        }
6✔
114

115
        p.mockserver = native.NewHTTPPact(p.config.Consumer, p.config.Provider)
18✔
116
        p.mockserver.WithMetadata("pact-go", "version", strings.TrimPrefix(command.Version, "v"))
18✔
117
        switch p.specificationVersion {
18✔
118
        case models.V2:
×
119
                p.mockserver.WithSpecificationVersion(native.SPECIFICATION_VERSION_V2)
×
UNCOV
120
        case models.V3:
×
UNCOV
121
                p.mockserver.WithSpecificationVersion(native.SPECIFICATION_VERSION_V3)
×
122
        case models.V4:
18✔
123
                p.mockserver.WithSpecificationVersion(native.SPECIFICATION_VERSION_V4)
18✔
124
        }
125
        native.Init(string(logging.LogLevel()))
18✔
126

18✔
127
        return nil
18✔
128
}
129

130
// ExecuteTest runs the current test case against a Mock Service.
131
// Will cleanup interactions between tests within a suite
132
// and write the pact file if successful
133
func (p *httpMockProvider) ExecuteTest(t *testing.T, integrationTest func(MockServerConfig) error) error {
12✔
134
        log.Println("[DEBUG] pact verify")
12✔
135

12✔
136
        var err error
12✔
137
        if p.config.AllowedMockServerPorts != "" && p.config.Port <= 0 {
12✔
UNCOV
138
                p.config.Port, err = utils.FindPortInRange(p.config.AllowedMockServerPorts)
×
139
        } else if p.config.Port <= 0 {
24✔
140
                p.config.Port, err = 0, nil
12✔
141
        }
12✔
142

143
        if err != nil {
12✔
UNCOV
144
                return fmt.Errorf("error: unable to find free port, mock server will fail to start")
×
UNCOV
145
        }
×
146

147
        p.config.Port, err = p.mockserver.Start(fmt.Sprintf("%s:%d", p.config.Host, p.config.Port), p.config.TLS)
12✔
148
        defer p.reset()
12✔
149
        if err != nil {
12✔
UNCOV
150
                return err
×
UNCOV
151
        }
×
152

153
        // Run the integration test
154
        err = integrationTest(MockServerConfig{
12✔
155
                Port:      p.config.Port,
12✔
156
                Host:      p.config.Host,
12✔
157
                TLSConfig: GetTLSConfigForTLSMockServer(),
12✔
158
        })
12✔
159

12✔
160
        res, mismatches := p.mockserver.Verify(p.config.Port, p.config.PactDir)
12✔
161
        p.displayMismatches(t, mismatches)
12✔
162

12✔
163
        if err != nil {
12✔
UNCOV
164
                return err
×
UNCOV
165
        }
×
166

167
        if !res {
24✔
168
                return fmt.Errorf("pact validation failed: %+v %+v", res, mismatches)
12✔
169
        }
12✔
170

171
        if len(mismatches) > 0 {
×
UNCOV
172
                return fmt.Errorf("pact validation failed: %+v", mismatches)
×
173
        }
×
174

175
        p.mockserver.CleanupPlugins()
×
UNCOV
176

×
UNCOV
177
        return p.writePact()
×
178
}
179

180
// Clear state between tests
181
func (p *httpMockProvider) reset() {
12✔
182
        p.mockserver.CleanupMockServer(p.config.Port)
12✔
183
        p.mockserver.CleanupPlugins()
12✔
184
        p.config.Port = 0
12✔
185
        err := p.configure()
12✔
186
        if err != nil {
12✔
UNCOV
187
                log.Println("[ERROR] failed to configure the mock server")
×
UNCOV
188
        }
×
189
}
190

191
// TODO: improve / pretty print this to make it really easy to understand the problems
192
// See existing Pact/Ruby code examples
193
func (p *httpMockProvider) displayMismatches(t *testing.T, mismatches []native.MismatchedRequest) {
12✔
194
        if len(mismatches) > 0 {
24✔
195

12✔
196
                if len(callerInfo()) > 0 {
24✔
197
                        fmt.Printf("\n\n%s:\n", callerInfo()[len(callerInfo())-1])
12✔
198
                }
12✔
199
                fmt.Println("\tPact Verification Failed for:", t.Name())
12✔
200
                fmt.Println()
12✔
201
                fmt.Println("\t\tDiff:")
12✔
202
                log.Println("[INFO] pact validation failed, errors: ")
12✔
203
                for _, m := range mismatches {
24✔
204
                        formattedRequest := fmt.Sprintf("%s %s", m.Request.Method, m.Request.Path)
12✔
205
                        switch m.Type {
12✔
206
                        case "missing-request":
12✔
207
                                fmt.Printf("\t\texpected: \t%s (Expected request that was not received)\n", formattedRequest)
12✔
208
                        case "request-not-found":
×
UNCOV
209
                                fmt.Printf("\t\tactual: \t%s (Unexpected request was received)\n", formattedRequest)
×
UNCOV
210
                        default:
×
211
                                // TODO:
212
                        }
213

214
                        for _, detail := range m.Mismatches {
12✔
215
                                switch detail.Type {
×
216
                                case "HeaderMismatch":
×
217
                                        fmt.Printf("\t\t\t%s\n:", detail.Mismatch)
×
218
                                        fmt.Println("\t\t\texpected: \t", detail.Expected)
×
219
                                        fmt.Println("\t\t\tactual: \t", detail.Actual)
×
220
                                case "BodyMismatch":
×
221
                                        fmt.Printf("\t\t\t%s\n", detail.Mismatch)
×
UNCOV
222
                                        fmt.Println("\t\t\texpected:\t", detail.Expected)
×
UNCOV
223
                                        fmt.Println("\t\t\tactual:\t\t", detail.Actual)
×
224
                                }
225
                        }
226
                }
227
                fmt.Println()
12✔
228
                fmt.Println()
12✔
229
        }
230
}
231

232
// Stolen from "github.com/stretchr/testify/assert"
233
func callerInfo() []string {
36✔
234

36✔
235
        var pc uintptr
36✔
236
        var ok bool
36✔
237
        var file string
36✔
238
        var line int
36✔
239
        var name string
36✔
240

36✔
241
        callers := []string{}
36✔
242
        for i := 0; ; i++ {
216✔
243
                pc, file, line, ok = runtime.Caller(i)
180✔
244
                if !ok {
180✔
245
                        // The breaks below failed to terminate the loop, and we ran off the
×
UNCOV
246
                        // end of the call stack.
×
UNCOV
247
                        break
×
248
                }
249

250
                // This is a huge edge case, but it will panic if this is the case, see #180
251
                if file == "<autogenerated>" {
180✔
UNCOV
252
                        break
×
253
                }
254

255
                f := runtime.FuncForPC(pc)
180✔
256
                if f == nil {
180✔
UNCOV
257
                        break
×
258
                }
259
                name = f.Name()
180✔
260

180✔
261
                // testing.tRunner is the standard library function that calls
180✔
262
                // tests. Subtests are called directly by tRunner, without going through
180✔
263
                // the Test/Benchmark/Example function that contains the t.Run calls, so
180✔
264
                // with subtests we should break when we hit tRunner, without adding it
180✔
265
                // to the list of callers.
180✔
266
                if name == "testing.tRunner" {
180✔
UNCOV
267
                        break
×
268
                }
269

270
                parts := strings.Split(file, "/")
180✔
271
                file = parts[len(parts)-1]
180✔
272
                if len(parts) > 1 {
360✔
273
                        dir := parts[len(parts)-2]
180✔
274
                        if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" {
360✔
275
                                callers = append(callers, fmt.Sprintf("%s:%d", file, line))
180✔
276
                        }
180✔
277
                }
278

279
                // Drop the package
280
                segments := strings.Split(name, ".")
180✔
281
                name = segments[len(segments)-1]
180✔
282
                if isTest(name, "Test") ||
180✔
283
                        isTest(name, "Benchmark") ||
180✔
284
                        isTest(name, "Example") {
216✔
285
                        break
36✔
286
                }
287
        }
288

289
        return callers
36✔
290
}
291

292
// Stolen from the `go test` tool.
293
// isTest tells whether name looks like a test (or benchmark, according to prefix).
294
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
295
// We don't want TesticularCancer.
296
func isTest(name, prefix string) bool {
468✔
297
        if !strings.HasPrefix(name, prefix) {
900✔
298
                return false
432✔
299
        }
432✔
300
        if len(name) == len(prefix) { // "Test" is ok
36✔
UNCOV
301
                return true
×
UNCOV
302
        }
×
303
        r, _ := utf8.DecodeRuneInString(name[len(prefix):])
36✔
304
        return !unicode.IsLower(r)
36✔
305
}
306

307
// writePact may be called after each interaction with a mock server is completed
308
// the shared core is threadsafe and will merge, as long as the requests come from a single process
309
// (that is, there isn't separate) instances of the FFI running simultaneously
310
func (p *httpMockProvider) writePact() error {
×
311
        log.Println("[DEBUG] write pact file")
×
312
        if p.config.Port != 0 {
×
313
                return p.mockserver.WritePactFile(p.config.Port, p.config.PactDir)
×
UNCOV
314
        }
×
UNCOV
315
        return errors.New("pact server not yet started")
×
316
}
317

318
// GetTLSConfigForTLSMockServer gets an http transport with
319
// the certificates already trusted. Alternatively, simply set
320
// trust level to insecure
321
func GetTLSConfigForTLSMockServer() *tls.Config {
12✔
322
        return native.GetTLSConfig()
12✔
323
}
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc