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

pact-foundation / pact-go / 6375236989

02 Oct 2023 02:24AM UTC coverage: 77.392% (-0.1%) from 77.52%
6375236989

push

github

web-flow
Merge pull request #344 from sagansystems/v1-acquire-ports-atomically

fix(verifier): acquire HTTP port atomically

20 of 28 new or added lines in 2 files covered. (71.43%)

4 existing lines in 2 files now uncovered.

1472 of 1902 relevant lines covered (77.39%)

10.56 hits per line

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

83.53
/dsl/pact.go
1
/*
2
Package dsl contains the main Pact DSL used in the Consumer
3
collaboration test cases, and Provider contract test verification.
4
*/
5
package dsl
6

7
import (
8
        "encoding/json"
9
        "errors"
10
        "fmt"
11
        "io/ioutil"
12
        "log"
13
        "net"
14
        "net/http"
15
        "net/url"
16
        "os"
17
        "path/filepath"
18
        "reflect"
19
        "strings"
20
        "testing"
21
        "time"
22

23
        "github.com/hashicorp/logutils"
24
        "github.com/pact-foundation/pact-go/install"
25
        "github.com/pact-foundation/pact-go/proxy"
26
        "github.com/pact-foundation/pact-go/types"
27
        "github.com/pact-foundation/pact-go/utils"
28
)
29

30
// Pact is the container structure to run the Consumer Pact test cases.
31
type Pact struct {
32
        // Current server for the consumer.
33
        Server *types.MockServer
34

35
        // Pact RPC Client.
36
        pactClient Client
37

38
        // Consumer is the name of the Consumer/Client.
39
        Consumer string
40

41
        // Provider is the name of the Providing service.
42
        Provider string
43

44
        // Interactions contains all of the Mock Service Interactions to be setup.
45
        Interactions []*Interaction
46

47
        // MessageInteractions contains all of the Message based interactions to be setup.
48
        MessageInteractions []*Message
49

50
        // Log levels.
51
        LogLevel string
52

53
        // Used to detect if logging has been configured.
54
        logFilter *logutils.LevelFilter
55

56
        // Location of Pact external service invocation output logging.
57
        // Defaults to `<cwd>/logs`.
58
        LogDir string
59

60
        // Pact files will be saved in this folder.
61
        // Defaults to `<cwd>/pacts`.
62
        PactDir string
63

64
        // PactFileWriteMode specifies how to write to the Pact file, for the life
65
        // of a Mock Service.
66
        // "overwrite" will always truncate and replace the pact after each run
67
        // "merge" will append to the pact file, which is useful if your tests
68
        // are split over multiple files and instantiations of a Mock Server
69
        PactFileWriteMode string
70

71
        // Specify which version of the Pact Specification should be used (1 or 2).
72
        // Defaults to 2.
73
        SpecificationVersion int
74

75
        // Host is the address of the Mock and Verification Service runs on
76
        // Examples include 'localhost', '127.0.0.1', '[::1]'
77
        // Defaults to 'localhost'
78
        Host string
79

80
        // Network is the network of the Mock and Verification Service
81
        // Examples include 'tcp', 'tcp4', 'tcp6'
82
        // Defaults to 'tcp'
83
        Network string
84

85
        // Ports MockServer can be deployed to, can be CSV or Range with a dash
86
        // Example "1234", "12324,5667", "1234-5667"
87
        AllowedMockServerPorts string
88

89
        // DisableToolValidityCheck prevents CLI version checking - use this carefully!
90
        // The ideal situation is to check the tool installation with  before running
91
        // the tests, which should speed up large test suites significantly
92
        DisableToolValidityCheck bool
93

94
        // ClientTimeout specifies how long to wait for Pact CLI to start
95
        // Can be increased to reduce likelihood of intermittent failure
96
        // Defaults to 10s
97
        ClientTimeout time.Duration
98

99
        // Check if CLI tools are up to date
100
        toolValidityCheck bool
101
}
102

103
// AddMessage creates a new asynchronous consumer expectation
104
func (p *Pact) AddMessage() *Message {
12✔
105
        p.setupLogging()
12✔
106
        log.Println("[DEBUG] pact add message")
12✔
107

12✔
108
        m := &Message{}
12✔
109
        p.MessageInteractions = append(p.MessageInteractions, m)
12✔
110
        return m
12✔
111
}
12✔
112

113
// AddInteraction creates a new Pact interaction, initialising all
114
// required things. Will automatically start a Mock Service if none running.
115
func (p *Pact) AddInteraction() *Interaction {
8✔
116
        p.Setup(true)
8✔
117
        log.Println("[DEBUG] pact add interaction")
8✔
118
        i := &Interaction{}
8✔
119
        p.Interactions = append(p.Interactions, i)
8✔
120
        return i
8✔
121
}
8✔
122

123
// Setup starts the Pact Mock Server. This is usually called before each test
124
// suite begins. AddInteraction() will automatically call this if no Mock Server
125
// has been started.
126
func (p *Pact) Setup(startMockServer bool) *Pact {
56✔
127
        p.setupLogging()
56✔
128
        log.Println("[DEBUG] pact setup")
56✔
129
        dir, _ := os.Getwd()
56✔
130

56✔
131
        if p.Network == "" {
106✔
132
                p.Network = "tcp"
50✔
133
        }
50✔
134

135
        if !p.toolValidityCheck && !(p.DisableToolValidityCheck || os.Getenv("PACT_DISABLE_TOOL_VALIDITY_CHECK") != "") {
106✔
136
                checkCliCompatibility()
50✔
137
                p.toolValidityCheck = true
50✔
138
        }
50✔
139

140
        if p.Host == "" {
106✔
141
                p.Host = "localhost"
50✔
142
        }
50✔
143

144
        if p.LogDir == "" {
106✔
145
                p.LogDir = filepath.Join(dir, "logs")
50✔
146
        }
50✔
147

148
        if p.PactDir == "" {
106✔
149
                p.PactDir = filepath.Join(dir, "pacts")
50✔
150
        }
50✔
151

152
        if p.SpecificationVersion == 0 {
106✔
153
                p.SpecificationVersion = 2
50✔
154
        }
50✔
155

156
        if p.ClientTimeout == 0 {
106✔
157
                p.ClientTimeout = 10 * time.Second
50✔
158
        }
50✔
159

160
        if p.pactClient == nil {
72✔
161
                c := NewClient()
16✔
162
                c.TimeoutDuration = p.ClientTimeout
16✔
163
                p.pactClient = c
16✔
164
        }
16✔
165

166
        if p.PactFileWriteMode == "" {
106✔
167
                p.PactFileWriteMode = "overwrite"
50✔
168
        }
50✔
169

170
        // Need to predefine due to scoping
171
        var port int
56✔
172
        var perr error
56✔
173
        if p.AllowedMockServerPorts != "" {
64✔
174
                port, perr = utils.FindPortInRange(p.AllowedMockServerPorts)
8✔
175
        } else {
56✔
176
                port, perr = utils.GetFreePort()
48✔
177
        }
48✔
178
        if perr != nil {
58✔
179
                log.Println("[ERROR] unable to find free port, mockserver will fail to start")
2✔
180
        }
2✔
181

182
        if p.Server == nil && startMockServer {
70✔
183
                log.Println("[DEBUG] starting mock service on port:", port)
14✔
184
                args := []string{
14✔
185
                        "--pact-specification-version",
14✔
186
                        fmt.Sprintf("%d", p.SpecificationVersion),
14✔
187
                        "--pact-dir",
14✔
188
                        filepath.FromSlash(p.PactDir),
14✔
189
                        "--log",
14✔
190
                        filepath.FromSlash(p.LogDir + "/" + "pact.log"),
14✔
191
                        "--consumer",
14✔
192
                        p.Consumer,
14✔
193
                        "--provider",
14✔
194
                        p.Provider,
14✔
195
                        "--pact-file-write-mode",
14✔
196
                        p.PactFileWriteMode,
14✔
197
                }
14✔
198

14✔
199
                p.Server = p.pactClient.StartServer(args, port)
14✔
200
        }
14✔
201

202
        return p
56✔
203
}
204

205
// Configure logging
206
func (p *Pact) setupLogging() {
74✔
207
        if p.logFilter == nil {
132✔
208
                if p.LogLevel == "" {
80✔
209
                        p.LogLevel = "INFO"
22✔
210
                }
22✔
211
                p.logFilter = &logutils.LevelFilter{
58✔
212
                        Levels:   []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARN", "ERROR"},
58✔
213
                        MinLevel: logutils.LogLevel(p.LogLevel),
58✔
214
                        Writer:   os.Stderr,
58✔
215
                }
58✔
216
                log.SetOutput(p.logFilter)
58✔
217
        }
218
        log.Println("[DEBUG] pact setup logging")
74✔
219
}
220

221
// Teardown stops the Pact Mock Server. This usually is called on completion
222
// of each test suite.
223
func (p *Pact) Teardown() *Pact {
4✔
224
        log.Println("[DEBUG] teardown")
4✔
225
        if p.Server != nil {
8✔
226
                server, err := p.pactClient.StopServer(p.Server)
4✔
227

4✔
228
                if err != nil {
4✔
229
                        log.Println("error:", err)
×
230
                }
×
231
                p.Server = server
4✔
232
        }
233
        return p
4✔
234
}
235

236
// Verify runs the current test case against a Mock Service.
237
// Will cleanup interactions between tests within a suite.
238
func (p *Pact) Verify(integrationTest func() error) error {
6✔
239
        p.Setup(true)
6✔
240
        log.Println("[DEBUG] pact verify")
6✔
241
        var err error
6✔
242

6✔
243
        // Check if we are verifying messages or if we actually have interactions
6✔
244
        if len(p.Interactions) == 0 {
8✔
245
                return errors.New("there are no interactions to be verified")
2✔
246
        }
2✔
247

248
        mockServer := &MockService{
4✔
249
                BaseURL:  fmt.Sprintf("http://%s:%d", p.Host, p.Server.Port),
4✔
250
                Consumer: p.Consumer,
4✔
251
                Provider: p.Provider,
4✔
252
        }
4✔
253

4✔
254
        // Cleanup all interactions
4✔
255
        defer func(mockServer *MockService) {
8✔
256
                log.Println("[DEBUG] clearing interactions")
4✔
257

4✔
258
                p.Interactions = make([]*Interaction, 0)
4✔
259
                err = mockServer.DeleteInteractions()
4✔
260
        }(mockServer)
4✔
261

262
        for _, interaction := range p.Interactions {
8✔
263
                err = mockServer.AddInteraction(interaction)
4✔
264
                if err != nil {
6✔
265
                        return err
2✔
266
                }
2✔
267
        }
268

269
        // Run the integration test
270
        err = integrationTest()
2✔
271
        if err != nil {
2✔
272
                return err
×
273
        }
×
274

275
        // Run Verification Process
276
        err = mockServer.Verify()
2✔
277
        if err != nil {
2✔
278
                return err
×
279
        }
×
280

281
        return err
2✔
282
}
283

284
// WritePact should be called writes when all tests have been performed for a
285
// given Consumer <-> Provider pair. It will write out the Pact to the
286
// configured file.
287
func (p *Pact) WritePact() error {
4✔
288
        p.Setup(true)
4✔
289
        log.Println("[DEBUG] pact write Pact file")
4✔
290
        mockServer := MockService{
4✔
291
                BaseURL:           fmt.Sprintf("http://%s:%d", p.Host, p.Server.Port),
4✔
292
                Consumer:          p.Consumer,
4✔
293
                Provider:          p.Provider,
4✔
294
                PactFileWriteMode: p.PactFileWriteMode,
4✔
295
        }
4✔
296
        err := mockServer.WritePact()
4✔
297
        if err != nil {
6✔
298
                return err
2✔
299
        }
2✔
300

301
        return nil
2✔
302
}
303

304
// VerifyProviderRaw reads the provided pact files and runs verification against
305
// a running Provider API, providing raw response from the Verification process.
306
//
307
// Order of events: BeforeEach, stateHandlers, requestFilter(pre <execute provider> post), AfterEach
308
func (p *Pact) VerifyProviderRaw(request types.VerifyRequest) ([]types.ProviderVerifierResponse, error) {
12✔
309
        p.Setup(false)
12✔
310
        res := make([]types.ProviderVerifierResponse, 0)
12✔
311

12✔
312
        u, err := url.Parse(request.ProviderBaseURL)
12✔
313

12✔
314
        if err != nil {
12✔
315
                return res, err
×
316
        }
×
317

318
        m := []proxy.Middleware{}
12✔
319

12✔
320
        if request.BeforeEach != nil {
14✔
321
                m = append(m, BeforeEachMiddleware(request.BeforeEach))
2✔
322
        }
2✔
323

324
        if request.AfterEach != nil {
14✔
325
                m = append(m, AfterEachMiddleware(request.AfterEach))
2✔
326
        }
2✔
327

328
        if len(request.StateHandlers) > 0 {
12✔
329
                m = append(m, stateHandlerMiddleware(request.StateHandlers))
×
330
        }
×
331

332
        if request.RequestFilter != nil {
14✔
333
                m = append(m, request.RequestFilter)
2✔
334
        }
2✔
335

336
        // Configure HTTP Verification Proxy
337
        opts := proxy.Options{
12✔
338
                TargetAddress:             fmt.Sprintf("%s:%s", u.Hostname(), u.Port()),
12✔
339
                TargetScheme:              u.Scheme,
12✔
340
                TargetPath:                u.Path,
12✔
341
                Middleware:                m,
12✔
342
                InternalRequestPathPrefix: providerStatesSetupPath,
12✔
343
                CustomTLSConfig:           request.CustomTLSConfig,
12✔
344
        }
12✔
345

12✔
346
        // Starts the message wrapper API with hooks back to the state handlers
12✔
347
        // This maps the 'description' field of a message pact, to a function handler
12✔
348
        // that will implement the message producer. This function must return an object and optionally
12✔
349
        // and error. The object will be marshalled to JSON for comparison.
12✔
350
        listener, err := proxy.HTTPReverseProxy(opts)
12✔
351
        if err != nil {
12✔
NEW
352
                log.Printf("[ERROR] unable to start http verification proxy: %v", err)
×
NEW
353
                return nil, err
×
NEW
354
        }
×
355
        defer listener.Close()
12✔
356

12✔
357
        port := listener.Addr().(*net.TCPAddr).Port
12✔
358

12✔
359
        // Backwards compatibility, setup old provider states URL if given
12✔
360
        // Otherwise point to proxy
12✔
361
        setupURL := request.ProviderStatesSetupURL
12✔
362
        if request.ProviderStatesSetupURL == "" && len(request.StateHandlers) > 0 {
12✔
363
                setupURL = fmt.Sprintf("http://localhost:%d%s", port, providerStatesSetupPath)
×
364
        }
×
365

366
        // Construct verifier request
367
        verificationRequest := types.VerifyRequest{
12✔
368
                ProviderBaseURL:            fmt.Sprintf("http://localhost:%d", port),
12✔
369
                PactURLs:                   request.PactURLs,
12✔
370
                BrokerURL:                  request.BrokerURL,
12✔
371
                Tags:                       request.Tags,
12✔
372
                BrokerUsername:             request.BrokerUsername,
12✔
373
                BrokerPassword:             request.BrokerPassword,
12✔
374
                BrokerToken:                request.BrokerToken,
12✔
375
                PublishVerificationResults: request.PublishVerificationResults,
12✔
376
                ProviderVersion:            request.ProviderVersion,
12✔
377
                Provider:                   request.Provider,
12✔
378
                ProviderStatesSetupURL:     setupURL,
12✔
379
                CustomProviderHeaders:      request.CustomProviderHeaders,
12✔
380
                ConsumerVersionSelectors:   request.ConsumerVersionSelectors,
12✔
381
                EnablePending:              request.EnablePending,
12✔
382
                ProviderTags:               request.ProviderTags,
12✔
383
                ProviderBranch:             request.ProviderBranch,
12✔
384
                Verbose:                    request.Verbose,
12✔
385
                FailIfNoPactsFound:         request.FailIfNoPactsFound,
12✔
386
                IncludeWIPPactsSince:       request.IncludeWIPPactsSince,
12✔
387
                PactLogDir:                 request.PactLogDir,
12✔
388
                PactLogLevel:               request.PactLogLevel,
12✔
389
        }
12✔
390

12✔
391
        if request.Provider == "" {
24✔
392
                verificationRequest.Provider = p.Provider
12✔
393
        }
12✔
394

395
        portErr := waitForPort(port, "tcp", "localhost", p.ClientTimeout,
12✔
396
                fmt.Sprintf(`Timed out waiting for http verification proxy on port %d - check for errors`, port))
12✔
397

12✔
398
        if portErr != nil {
12✔
399
                log.Fatal("Error:", err)
×
400
                return res, portErr
×
401
        }
×
402

403
        log.Println("[DEBUG] pact provider verification")
12✔
404

12✔
405
        return p.pactClient.VerifyProvider(verificationRequest)
12✔
406
}
407

408
// VerifyProvider accepts an instance of `*testing.T`
409
// running the provider verification with granular test reporting and
410
// automatic failure reporting for nice, simple tests.
411
func (p *Pact) VerifyProvider(t *testing.T, request types.VerifyRequest) ([]types.ProviderVerifierResponse, error) {
6✔
412
        res, err := p.VerifyProviderRaw(request)
6✔
413

6✔
414
        if len(res) == 0 {
12✔
415
                var message = "no pacts found to verify"
6✔
416
                if err != nil {
10✔
417
                        message = "error verifying the provider: see returned error for detail"
4✔
418
                }
4✔
419

420
                if len(request.Tags) > 0 {
6✔
421
                        message = fmt.Sprintf("%s. Check the tags provided (%s) for your broker (%s) are correct", message, strings.Join(request.Tags, ","), request.BrokerURL)
×
422
                }
×
423

424
                if request.FailIfNoPactsFound {
6✔
425
                        t.Errorf(message)
×
426
                } else {
6✔
427
                        t.Logf(message)
6✔
428
                }
6✔
429
        }
430

431
        runTestCases(t, res)
6✔
432

6✔
433
        return res, err
6✔
434
}
435

436
var installer = install.NewInstaller()
437

438
var checkCliCompatibility = func() {
×
439
        log.Println("[DEBUG] checking CLI compatibility")
×
440
        err := installer.CheckInstallation()
×
441

×
442
        if err != nil {
×
443
                log.Fatal("[ERROR] CLI tools are out of date, please upgrade before continuing")
×
444
        }
×
445
}
446

447
// BeforeEachMiddleware is invoked before any other, only on the __setup
448
// request (to avoid duplication)
449
func BeforeEachMiddleware(beforeEach types.Hook) proxy.Middleware {
6✔
450
        return func(next http.Handler) http.Handler {
10✔
451
                return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
8✔
452
                        if r.URL.Path == providerStatesSetupPath {
6✔
453

2✔
454
                                log.Println("[DEBUG] executing before hook")
2✔
455
                                err := beforeEach()
2✔
456

2✔
457
                                if err != nil {
2✔
458
                                        log.Println("[ERROR] error executing before hook:", err)
×
459
                                        w.WriteHeader(http.StatusInternalServerError)
×
460
                                }
×
461
                        }
462
                        next.ServeHTTP(w, r)
4✔
463
                })
464
        }
465
}
466

467
// AfterEachMiddleware is invoked after any other, and is the last
468
// function to be called prior to returning to the test suite. It is
469
// therefore not invoked on __setup
470
func AfterEachMiddleware(afterEach types.Hook) proxy.Middleware {
6✔
471
        return func(next http.Handler) http.Handler {
10✔
472
                return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
8✔
473
                        next.ServeHTTP(w, r)
4✔
474

4✔
475
                        if r.URL.Path != providerStatesSetupPath {
6✔
476
                                log.Println("[DEBUG] executing after hook")
2✔
477
                                err := afterEach()
2✔
478

2✔
479
                                if err != nil {
2✔
480
                                        log.Println("[ERROR] error executing after hook:", err)
×
481
                                        w.WriteHeader(http.StatusInternalServerError)
×
482
                                }
×
483
                        }
484
                })
485
        }
486
}
487

488
// stateHandlerMiddleware responds to the various states that are
489
// given during provider verification
490
//
491
// statehandler accepts a state object from the verifier and executes
492
// any state handlers associated with the provider.
493
// It will not execute further middleware if it is the designted "state" request
494
func stateHandlerMiddleware(stateHandlers types.StateHandlers) proxy.Middleware {
8✔
495
        return func(next http.Handler) http.Handler {
16✔
496
                return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16✔
497
                        if r.URL.Path == providerStatesSetupPath {
14✔
498
                                var s types.ProviderState
6✔
499
                                decoder := json.NewDecoder(r.Body)
6✔
500
                                if err := decoder.Decode(&s); err != nil {
6✔
501
                                        log.Printf("[ERROR] failed to decode provider state: %v", err)
×
502
                                        w.WriteHeader(http.StatusBadRequest)
×
503
                                        return
×
504
                                }
×
505

506
                                // Setup any provider state
507
                                for _, state := range s.States {
12✔
508
                                        sf, stateFound := stateHandlers[state]
6✔
509

6✔
510
                                        if !stateFound {
8✔
511
                                                log.Printf("[WARN] state handler not found for state: %v", state)
2✔
512
                                        } else {
6✔
513
                                                // Execute state handler
4✔
514
                                                if err := sf(); err != nil {
6✔
515
                                                        log.Printf("[ERROR] state handler for '%v' errored: %v", state, err)
2✔
516
                                                        w.WriteHeader(http.StatusInternalServerError)
2✔
517
                                                        return
2✔
518
                                                }
2✔
519
                                        }
520
                                }
521

522
                                w.WriteHeader(http.StatusOK)
4✔
523
                                return
4✔
524
                        }
525

526
                        log.Println("[DEBUG] skipping state handler for request", r.RequestURI)
2✔
527

2✔
528
                        // Pass through to application
2✔
529
                        next.ServeHTTP(w, r)
2✔
530
                })
531
        }
532
}
533

534
var messageVerificationHandler = func(messageHandlers MessageHandlers, stateHandlers StateHandlers) http.HandlerFunc {
12✔
535
        return func(w http.ResponseWriter, r *http.Request) {
22✔
536
                w.Header().Set("Content-Type", "application/json; charset=utf-8")
10✔
537

10✔
538
                // Extract message
10✔
539
                var message Message
10✔
540
                body, err := ioutil.ReadAll(r.Body)
10✔
541
                r.Body.Close()
10✔
542

10✔
543
                if err != nil {
10✔
544
                        w.WriteHeader(http.StatusBadRequest)
×
545
                        return
×
546
                }
×
547

548
                err = json.Unmarshal(body, &message)
10✔
549

10✔
550
                if err != nil {
12✔
551
                        w.WriteHeader(http.StatusBadRequest)
2✔
552
                        return
2✔
553
                }
2✔
554

555
                // Setup any provider state
556
                for _, state := range message.States {
14✔
557
                        sf, stateFound := stateHandlers[state.Name]
6✔
558

6✔
559
                        if !stateFound {
6✔
560
                                log.Printf("[WARN] state handler not found for state: %v", state.Name)
×
561
                        } else {
6✔
562
                                // Execute state handler
6✔
563
                                if err = sf(state); err != nil {
8✔
564
                                        log.Printf("[WARN] state handler for '%v' return error: %v", state.Name, err)
2✔
565
                                        w.WriteHeader(http.StatusInternalServerError)
2✔
566
                                        return
2✔
567
                                }
2✔
568
                        }
569
                }
570

571
                // Lookup key in function mapping
572
                f, messageFound := messageHandlers[message.Description]
6✔
573

6✔
574
                if !messageFound {
8✔
575
                        log.Printf("[ERROR] message handler not found for message description: %v", message.Description)
2✔
576
                        w.WriteHeader(http.StatusNotFound)
2✔
577
                        return
2✔
578
                }
2✔
579

580
                // Execute function handler
581
                res, handlerErr := f(message)
4✔
582

4✔
583
                if handlerErr != nil {
6✔
584
                        w.WriteHeader(http.StatusServiceUnavailable)
2✔
585
                        return
2✔
586
                }
2✔
587

588
                wrappedResponse := map[string]interface{}{
2✔
589
                        "contents": res,
2✔
590
                }
2✔
591

2✔
592
                // Write the body back
2✔
593
                resBody, errM := json.Marshal(wrappedResponse)
2✔
594
                if errM != nil {
2✔
595
                        w.WriteHeader(http.StatusServiceUnavailable)
×
596
                        log.Println("[ERROR] error marshalling object:", errM)
×
597
                        return
×
598
                }
×
599

600
                w.WriteHeader(http.StatusOK)
2✔
601
                if _, err := w.Write(resBody); err != nil {
2✔
602
                        log.Println("[ERROR] error writing response:", err)
×
603
                }
×
604
        }
605
}
606

607
func generateTestCaseName(res types.ProviderVerifierResponse) string {
×
608
        if len(res.Examples) > 1 {
×
609
                return fmt.Sprintf("Pact between %s and %s %s", res.Examples[0].Pact.ConsumerName, res.Examples[0].Pact.ProviderName, res.Examples[0].Pact.ShortDescription)
×
610
        }
×
611
        return "Running pact test"
×
612
}
613

614
// VerifyMessageProvider accepts an instance of `*testing.T`
615
// running provider message verification with granular test reporting and
616
// automatic failure reporting for nice, simple tests.
617
//
618
// A Message Producer is analogous to Consumer in the HTTP Interaction model.
619
// It is the initiator of an interaction, and expects something on the other end
620
// of the interaction to respond - just in this case, not immediately.
621
func (p *Pact) VerifyMessageProvider(t *testing.T, request VerifyMessageRequest) (res []types.ProviderVerifierResponse, err error) {
2✔
622
        res, err = p.VerifyMessageProviderRaw(request)
2✔
623

2✔
624
        runTestCases(t, res)
2✔
625

2✔
626
        return
2✔
627
}
2✔
628

629
func runTestCases(t *testing.T, res []types.ProviderVerifierResponse) {
8✔
630
        for _, test := range res {
8✔
631
                t.Run(generateTestCaseName(test), func(pactTest *testing.T) {
×
632
                        for _, notice := range test.Summary.Notices {
×
633
                                if notice.When == "before_verification" {
×
634
                                        t.Logf("notice: %s", notice.Text)
×
635
                                }
×
636
                        }
637
                        for _, example := range test.Examples {
×
638
                                testCase := example.Description
×
639
                                if example.Status == "pending" {
×
640
                                        testCase = fmt.Sprintf("Pending %s", example.Description)
×
641
                                }
×
642

643
                                t.Run(testCase, func(st *testing.T) {
×
644
                                        st.Log(example.FullDescription)
×
645

×
646
                                        if example.Status != "passed" {
×
647
                                                if example.Status == "pending" {
×
648
                                                        st.Skip(example.Exception.Message)
×
649
                                                } else {
×
650
                                                        st.Errorf("%s\n%s\n", example.FullDescription, example.Exception.Message)
×
651
                                                }
×
652
                                        }
653
                                })
654
                        }
655
                        for _, notice := range test.Summary.Notices {
×
656
                                if notice.When == "after_verification" {
×
657
                                        t.Logf("notice: %s", notice.Text)
×
658
                                }
×
659
                        }
660
                })
661
        }
662
}
663

664
// VerifyMessageProviderRaw runs provider message verification.
665
//
666
// A Message Producer is analogous to Consumer in the HTTP Interaction model.
667
// It is the initiator of an interaction, and expects something on the other end
668
// of the interaction to respond - just in this case, not immediately.
669
func (p *Pact) VerifyMessageProviderRaw(request VerifyMessageRequest) ([]types.ProviderVerifierResponse, error) {
2✔
670
        p.Setup(false)
2✔
671
        response := make([]types.ProviderVerifierResponse, 0)
2✔
672

2✔
673
        // Starts the message wrapper API with hooks back to the message handlers
2✔
674
        // This maps the 'description' field of a message pact, to a function handler
2✔
675
        // that will implement the message producer. This function must return an object and optionally
2✔
676
        // and error. The object will be marshalled to JSON for comparison.
2✔
677
        mux := http.NewServeMux()
2✔
678

2✔
679
        listener, err := net.Listen("tcp", "localhost:0")
2✔
680
        if err != nil {
2✔
NEW
681
                log.Printf("[ERROR] unable to allocate a port for verification: %v", err)
×
NEW
682
                return nil, err
×
UNCOV
683
        }
×
684
        defer listener.Close()
2✔
685

2✔
686
        log.Printf("[DEBUG] API handler starting at %s", listener.Addr())
2✔
687

2✔
688
        // Construct verifier request
2✔
689
        verificationRequest := types.VerifyRequest{
2✔
690
                ProviderBaseURL:            fmt.Sprintf("http://%s", listener.Addr()),
2✔
691
                PactURLs:                   request.PactURLs,
2✔
692
                BrokerURL:                  request.BrokerURL,
2✔
693
                Tags:                       request.Tags,
2✔
694
                ConsumerVersionSelectors:   request.ConsumerVersionSelectors,
2✔
695
                BrokerUsername:             request.BrokerUsername,
2✔
696
                BrokerPassword:             request.BrokerPassword,
2✔
697
                BrokerToken:                request.BrokerToken,
2✔
698
                PublishVerificationResults: request.PublishVerificationResults,
2✔
699
                ProviderVersion:            request.ProviderVersion,
2✔
700
                TagWithGitBranch:           request.TagWithGitBranch,
2✔
701
                ProviderTags:               request.ProviderTags,
2✔
702
                Provider:                   p.Provider,
2✔
703
                PactLogDir:                 p.LogDir,
2✔
704
                PactLogLevel:               p.LogLevel,
2✔
705
        }
2✔
706

2✔
707
        mux.HandleFunc("/", messageVerificationHandler(request.MessageHandlers, request.StateHandlers))
2✔
708

2✔
709
        go func() {
4✔
710
                if err := http.Serve(listener, mux); err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") {
2✔
UNCOV
711
                        log.Printf("[DEBUG] API handler start failed: %v", err)
×
UNCOV
712
                }
×
713
        }()
714

715
        port := listener.Addr().(*net.TCPAddr).Port
2✔
716

2✔
717
        portErr := waitForPort(port, "tcp", "localhost", p.ClientTimeout,
2✔
718
                fmt.Sprintf(`Timed out waiting for pact proxy on port %d - check for errors`, port))
2✔
719

2✔
720
        if portErr != nil {
2✔
721
                return response, portErr
×
722
        }
×
723

724
        log.Println("[DEBUG] pact provider verification")
2✔
725
        return p.pactClient.VerifyProvider(verificationRequest)
2✔
726
}
727

728
// VerifyMessageConsumerRaw creates a new Pact _message_ interaction to build a testable
729
// interaction.
730
//
731
// A Message Consumer is analogous to a Provider in the HTTP Interaction model.
732
// It is the receiver of an interaction, and needs to be able to handle whatever
733
// request was provided.
734
func (p *Pact) VerifyMessageConsumerRaw(message *Message, handler MessageConsumer) error {
10✔
735
        log.Println("[DEBUG] verify message")
10✔
736
        p.Setup(false)
10✔
737

10✔
738
        // Reify the message back to its "example/generated" form
10✔
739
        reified, err := p.pactClient.ReifyMessage(&types.PactReificationRequest{
10✔
740
                Message: message.Content,
10✔
741
        })
10✔
742

10✔
743
        if err != nil {
12✔
744
                return fmt.Errorf("unable to convert consumer test to a valid JSON representation: %v", err)
2✔
745
        }
2✔
746

747
        t := reflect.TypeOf(message.Type)
8✔
748
        if t != nil && t.Name() != "interface" {
10✔
749
                log.Println("[DEBUG] narrowing type to", t.Name())
2✔
750
                err = json.Unmarshal(reified.ResponseRaw, &message.Type)
2✔
751

2✔
752
                if err != nil {
2✔
753
                        return fmt.Errorf("unable to narrow type to %v: %v", t.Name(), err)
×
754
                }
×
755
        }
756

757
        // Yield message, and send through handler function
758
        generatedMessage :=
8✔
759
                Message{
8✔
760
                        Content:     message.Type,
8✔
761
                        States:      message.States,
8✔
762
                        Description: message.Description,
8✔
763
                        Metadata:    message.Metadata,
8✔
764
                }
8✔
765

8✔
766
        err = handler(generatedMessage)
8✔
767
        if err != nil {
10✔
768
                return err
2✔
769
        }
2✔
770

771
        // If no errors, update Message Pact
772
        return p.pactClient.UpdateMessagePact(types.PactMessageRequest{
6✔
773
                Message:  message,
6✔
774
                Consumer: p.Consumer,
6✔
775
                Provider: p.Provider,
6✔
776
                PactDir:  p.PactDir,
6✔
777
        })
6✔
778
}
779

780
// VerifyMessageConsumer is a test convenience function for VerifyMessageConsumerRaw,
781
// accepting an instance of `*testing.T`
782
func (p *Pact) VerifyMessageConsumer(t *testing.T, message *Message, handler MessageConsumer) error {
6✔
783
        err := p.VerifyMessageConsumerRaw(message, handler)
6✔
784

6✔
785
        if err != nil {
8✔
786
                t.Errorf("VerifyMessageConsumer failed: %v", err)
2✔
787
        }
2✔
788

789
        return err
6✔
790
}
791

792
const providerStatesSetupPath = "/__setup"
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