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

pact-foundation / pact-go / 18959722011

31 Oct 2025 01:17AM UTC coverage: 30.963% (+2.7%) from 28.272%
18959722011

Pull #455

github

YOU54F
test(fix): avro - non-constant format string in call to fmt.Fprintf
Pull Request #455: Feat/purego

380 of 445 new or added lines in 5 files covered. (85.39%)

1 existing line in 1 file now uncovered.

1999 of 6456 relevant lines covered (30.96%)

16.61 hits per line

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

68.26
/internal/native/message_server.go
1
package native
2

3
import (
4
        "encoding/json"
5
        "errors"
6
        "fmt"
7
        "log"
8

9
        "unsafe"
10
)
11

12
type MessagePact struct {
13
        handle uintptr
14
}
15

16
type messageType int
17

18
const (
19
        MESSAGE_TYPE_ASYNC messageType = iota
20
        MESSAGE_TYPE_SYNC
21
)
22

23
type Message struct {
24
        handle      uintptr
25
        messageType messageType
26
        pact        *MessagePact
27
        index       int
28
        server      *MessageServer
29
}
30

31
// MessageServer is the public interface for managing the message based interface
32
type MessageServer struct {
33
        messagePact *MessagePact
34
        messages    []*Message
35
}
36

37
// NewMessage initialises a new message for the current contract
38
func NewMessageServer(consumer string, provider string) *MessageServer {
132✔
39
        // cConsumer := C.CString(consumer)
132✔
40
        // cProvider := C.CString(provider)
132✔
41
        // defer free(cConsumer)
132✔
42
        // defer free(cProvider)
132✔
43

132✔
44
        return &MessageServer{messagePact: &MessagePact{handle: pactffi_new_message_pact(consumer, provider)}}
132✔
45
}
132✔
46

47
// Sets the additional metadata on the Pact file. Common uses are to add the client library details such as the name and version
48
func (m *MessageServer) WithMetadata(namespace, k, v string) *MessageServer {
12✔
49
        // cNamespace := C.CString(namespace)
12✔
50
        // defer free(cNamespace)
12✔
51
        // cName := C.CString(k)
12✔
52
        // defer free(cName)
12✔
53
        // cValue := C.CString(v)
12✔
54
        // defer free(cValue)
12✔
55

12✔
56
        pactffi_with_message_pact_metadata(m.messagePact.handle, namespace, k, v)
12✔
57

12✔
58
        return m
12✔
59
}
12✔
60

61
// NewMessage initialises a new message for the current contract
62
// Deprecated: use NewAsyncMessageInteraction instead
63
func (m *MessageServer) NewMessage() *Message {
48✔
64
        // Alias
48✔
65
        return m.NewAsyncMessageInteraction("")
48✔
66
}
48✔
67

68
// NewSyncMessageInteraction initialises a new synchronous message interaction for the current contract
69
func (m *MessageServer) NewSyncMessageInteraction(description string) *Message {
72✔
70

72✔
71
        i := &Message{
72✔
72
                handle:      pactffi_new_sync_message_interaction(m.messagePact.handle, description),
72✔
73
                messageType: MESSAGE_TYPE_SYNC,
72✔
74
                pact:        m.messagePact,
72✔
75
                index:       len(m.messages),
72✔
76
                server:      m,
72✔
77
        }
72✔
78
        m.messages = append(m.messages, i)
72✔
79

72✔
80
        return i
72✔
81
}
72✔
82

83
// NewAsyncMessageInteraction initialises a new asynchronous message interaction for the current contract
84
func (m *MessageServer) NewAsyncMessageInteraction(description string) *Message {
60✔
85

60✔
86
        i := &Message{
60✔
87
                handle:      pactffi_new_message_interaction(m.messagePact.handle, description),
60✔
88
                messageType: MESSAGE_TYPE_ASYNC,
60✔
89
                pact:        m.messagePact,
60✔
90
                index:       len(m.messages),
60✔
91
                server:      m,
60✔
92
        }
60✔
93
        m.messages = append(m.messages, i)
60✔
94

60✔
95
        return i
60✔
96
}
60✔
97

98
func (m *MessageServer) WithSpecificationVersion(version specificationVersion) {
×
NEW
99
        pactffi_with_specification(m.messagePact.handle, int32(version))
×
100
}
×
101

102
func (m *Message) Given(state string) *Message {
132✔
103
        pactffi_given(m.handle, state)
132✔
104

132✔
105
        return m
132✔
106
}
132✔
107

108
func (m *Message) GivenWithParameter(state string, params map[string]interface{}) *Message {
72✔
109
        if len(params) == 0 {
72✔
NEW
110
                pactffi_given(m.handle, state)
×
111
        } else {
72✔
112
                for k, v := range params {
144✔
113
                        param := stringFromInterface(v)
72✔
114
                        pactffi_given_with_param(m.handle, state, k, param)
72✔
115
                }
72✔
116
        }
117

118
        return m
72✔
119
}
120

121
func (m *Message) ExpectsToReceive(description string) *Message {
72✔
122
        pactffi_message_expects_to_receive(m.handle, description)
72✔
123

72✔
124
        return m
72✔
125
}
72✔
126

127
func (m *Message) WithMetadata(valueOrMatcher map[string]string) *Message {
72✔
128
        for k, v := range valueOrMatcher {
144✔
129
                // TODO: check if matching rules allowed here
72✔
130
                // value := stringFromInterface(v)
72✔
131
                // fmt.Printf("withheaders, sending: %+v \n\n", value)
72✔
132
                // cValue := C.CString(value)
72✔
133

72✔
134
                pactffi_message_with_metadata(m.handle, k, v)
72✔
135
        }
72✔
136

137
        return m
72✔
138
}
139
func (m *Message) WithRequestMetadata(valueOrMatcher map[string]string) *Message {
×
140
        for k, v := range valueOrMatcher {
×
NEW
141
                pactffi_with_metadata(m.handle, k, v, 0)
×
142
        }
×
143

144
        return m
×
145
}
146
func (m *Message) WithResponseMetadata(valueOrMatcher map[string]string) *Message {
×
147
        for k, v := range valueOrMatcher {
×
NEW
148
                pactffi_with_metadata(m.handle, k, v, 1)
×
149
        }
×
150

151
        return m
×
152
}
153

154
func (m *Message) WithRequestBinaryContents(body []byte) *Message {
×
155

×
156
        // TODO: handle response
×
NEW
157
        res := pactffi_with_binary_file(m.handle, int32(INTERACTION_PART_REQUEST), "application/octet-stream", string(body), size_t(len(body)))
×
158

×
NEW
159
        log.Println("[DEBUG] WithRequestBinaryContents - pactffi_with_binary_file returned", res)
×
160

×
161
        return m
×
162
}
×
163
func (m *Message) WithRequestBinaryContentType(contentType string, body []byte) *Message {
12✔
164

12✔
165
        // TODO: handle response
12✔
166
        res := pactffi_with_binary_file(m.handle, int32(INTERACTION_PART_REQUEST), contentType, string(body), size_t(len(body)))
12✔
167

12✔
168
        log.Println("[DEBUG] WithRequestBinaryContents - pactffi_with_binary_file returned", res)
12✔
169

12✔
170
        return m
12✔
171
}
12✔
172

173
func (m *Message) WithRequestJSONContents(body interface{}) *Message {
24✔
174
        value := stringFromInterface(body)
24✔
175

24✔
176
        log.Println("[DEBUG] message WithJSONContents", value)
24✔
177

24✔
178
        return m.WithContents(INTERACTION_PART_REQUEST, "application/json", []byte(value))
24✔
179
}
24✔
180

181
func (m *Message) WithResponseBinaryContents(body []byte) *Message {
×
182

×
183
        // TODO: handle response
×
NEW
184
        pactffi_with_binary_file(m.handle, int32(INTERACTION_PART_RESPONSE), "application/octet-stream", string(body), size_t(len(body)))
×
185

×
186
        return m
×
187
}
×
188

189
func (m *Message) WithResponseJSONContents(body interface{}) *Message {
12✔
190
        value := stringFromInterface(body)
12✔
191

12✔
192
        log.Println("[DEBUG] message WithJSONContents", value)
12✔
193

12✔
194
        return m.WithContents(INTERACTION_PART_RESPONSE, "application/json", []byte(value))
12✔
195
}
12✔
196

197
// Note that string values here must be NUL terminated.
198
func (m *Message) WithContents(part interactionPart, contentType string, body []byte) *Message {
48✔
199

48✔
200
        res := pactffi_with_body(m.handle, int32(part), contentType, string(body))
48✔
201
        log.Println("[DEBUG] response from pactffi_interaction_contents", res)
48✔
202

48✔
203
        return m
48✔
204
}
48✔
205

206
// TODO: migrate plugin code to shared struct/code?
207

208
// NewInteraction initialises a new interaction for the current contract
209
func (m *MessageServer) UsingPlugin(pluginName string, pluginVersion string) error {
60✔
210

60✔
211
        r := pactffi_using_plugin(m.messagePact.handle, pluginName, pluginVersion)
60✔
212
        // 1 - A general panic was caught.
60✔
213
        // 2 - Failed to load the plugin.
60✔
214
        // 3 - Pact Handle is not valid.
60✔
215
        res := int(r)
60✔
216
        switch res {
60✔
217
        case 1:
×
218
                return ErrPluginGenericPanic
×
219
        case 2:
×
220
                return ErrPluginFailed
×
221
        case 3:
×
222
                return ErrHandleNotFound
×
223
        default:
60✔
224
                if res != 0 {
60✔
225
                        return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code:", res)
×
226
                }
×
227
        }
228

229
        return nil
60✔
230
}
231

232
// NewInteraction initialises a new interaction for the current contract
233
func (m *Message) WithPluginInteractionContents(part interactionPart, contentType string, contents string) error {
60✔
234

60✔
235
        r := pactffi_interaction_contents(m.handle, int32(part), contentType, contents)
60✔
236

60✔
237
        // 1 - A general panic was caught.
60✔
238
        // 2 - The mock server has already been started.
60✔
239
        // 3 - The interaction handle is invalid.
60✔
240
        // 4 - The content type is not valid.
60✔
241
        // 5 - The contents JSON is not valid JSON.
60✔
242
        // 6 - The plugin returned an error.
60✔
243
        res := int(r)
60✔
244
        switch res {
60✔
245
        case 1:
×
246
                return ErrPluginGenericPanic
×
247
        case 2:
×
248
                return ErrPluginMockServerStarted
×
249
        case 3:
×
250
                return ErrPluginInteractionHandleInvalid
×
251
        case 4:
×
252
                return ErrPluginInvalidContentType
×
253
        case 5:
×
254
                return ErrPluginInvalidJson
×
255
        case 6:
×
256
                return ErrPluginSpecificError
×
257
        default:
60✔
258
                if res != 0 {
60✔
259
                        return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code:", res)
×
260
                }
×
261
        }
262

263
        return nil
60✔
264
}
265

266
func GoByteArrayFromC(ptr uintptr, length int) []byte {
108✔
267
        if unsafe.Pointer(ptr) == nil || length == 0 {
108✔
NEW
268
                return []byte{}
×
NEW
269
        }
×
270
        // Create a Go byte slice from the C string
271
        b := make([]byte, length)
108✔
272
        for i := 0; i < length; i++ {
2,568✔
273
                b[i] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + uintptr(i)))
2,460✔
274
        }
2,460✔
275
        return b
108✔
276
}
277

278
// GetMessageContents retreives the binary contents of the request for a given message
279
// any matchers are stripped away if given
280
// if the contents is from a plugin, the byte[] representation of the parsed
281
// plugin data is returned, again, with any matchers etc. removed
282
func (m *Message) GetMessageRequestContents() ([]byte, error) {
84✔
283
        log.Println("[DEBUG] GetMessageRequestContents")
84✔
284
        if m.messageType == MESSAGE_TYPE_ASYNC {
144✔
285
                iter := pactffi_pact_handle_get_message_iter(m.pact.handle)
60✔
286
                log.Println("[DEBUG] pactffi_pact_handle_get_message_iter")
60✔
287
                // TODO
60✔
288
                if unsafe.Pointer(iter) == nil {
60✔
289
                        return nil, errors.New("unable to get a message iterator")
×
290
                }
×
291
                log.Println("[DEBUG] pactffi_pact_handle_get_message_iter - OK")
60✔
292

60✔
293
                ///////
60✔
294
                // TODO: some debugging in here to see what's exploding.......
60✔
295
                ///////
60✔
296

60✔
297
                log.Println("[DEBUG] pactffi_pact_handle_get_message_iter - len", len(m.server.messages))
60✔
298

60✔
299
                for i := 0; i < len(m.server.messages); i++ {
120✔
300
                        log.Println("[DEBUG] pactffi_pact_handle_get_message_iter - index", i)
60✔
301
                        message := pactffi_pact_message_iter_next(iter)
60✔
302
                        log.Println("[DEBUG] pactffi_pact_message_iter_next - message", message)
60✔
303

60✔
304
                        if i == m.index {
120✔
305
                                log.Println("[DEBUG] pactffi_pact_message_iter_next - index match", message)
60✔
306

60✔
307
                                if unsafe.Pointer(message) == nil {
60✔
308
                                        return nil, errors.New("retrieved a null message pointer")
×
309
                                }
×
310
                                len := pactffi_message_get_contents_length(message)
60✔
311
                                log.Println("[DEBUG] pactffi_message_get_contents_length - len", len)
60✔
312
                                if len == 0 {
60✔
313
                                        // You can have empty bodies
×
314
                                        log.Println("[DEBUG] message body is empty")
×
NEW
315
                                        return []byte{}, nil
×
316
                                }
×
317
                                data := pactffi_message_get_contents_bin(message)
60✔
318
                                log.Println("[DEBUG] pactffi_message_get_contents_bin - data", data)
60✔
319
                                if unsafe.Pointer(data) == nil {
60✔
320
                                        // You can have empty bodies
×
321
                                        log.Println("[DEBUG] message binary contents are empty")
×
322
                                        return nil, nil
×
323
                                }
×
324
                                bytes := GoByteArrayFromC(data, int(len))
60✔
325
                                return bytes, nil
60✔
326
                        }
327
                }
328

329
        } else {
24✔
330
                iter := pactffi_pact_handle_get_sync_message_iter(m.pact.handle)
24✔
331
                if unsafe.Pointer(iter) == nil {
24✔
332
                        return nil, errors.New("unable to get a message iterator")
×
333
                }
×
334

335
                for i := 0; i < len(m.server.messages); i++ {
48✔
336
                        message := pactffi_pact_sync_message_iter_next(iter)
24✔
337

24✔
338
                        if i == m.index {
48✔
339
                                if unsafe.Pointer(message) == nil {
24✔
340
                                        return nil, errors.New("retrieved a null message pointer")
×
341
                                }
×
342

343
                                len := pactffi_sync_message_get_request_contents_length(message)
24✔
344
                                if len == 0 {
24✔
345
                                        log.Println("[DEBUG] message body is empty")
×
NEW
346
                                        return []byte{}, nil
×
347
                                }
×
348
                                data := pactffi_sync_message_get_request_contents_bin(message)
24✔
349
                                if unsafe.Pointer(data) == nil {
24✔
350
                                        log.Println("[DEBUG] message binary contents are empty")
×
351
                                        return nil, nil
×
352
                                }
×
353
                                bytes := GoByteArrayFromC(data, int(len))
24✔
354

24✔
355
                                return bytes, nil
24✔
356
                        }
357
                }
358
        }
359

360
        return nil, errors.New("unable to find the message")
×
361
}
362

363
// GetMessageResponseContents retreives the binary contents of the response for a given message
364
// any matchers are stripped away if given
365
// if the contents is from a plugin, the byte[] representation of the parsed
366
// plugin data is returned, again, with any matchers etc. removed
367
func (m *Message) GetMessageResponseContents() ([][]byte, error) {
48✔
368

48✔
369
        responses := make([][]byte, len(m.server.messages))
48✔
370
        if m.messageType == MESSAGE_TYPE_ASYNC {
48✔
371
                return nil, errors.New("invalid request: asynchronous messages do not have response")
×
372
        }
×
373
        iter := pactffi_pact_handle_get_sync_message_iter(m.pact.handle)
48✔
374
        if unsafe.Pointer(iter) == nil {
48✔
375
                return nil, errors.New("unable to get a message iterator")
×
376
        }
×
377

378
        for i := 0; i < len(m.server.messages); i++ {
96✔
379
                message := pactffi_pact_sync_message_iter_next(iter)
48✔
380

48✔
381
                if unsafe.Pointer(message) == nil {
48✔
382
                        return nil, errors.New("retrieved a null message pointer")
×
383
                }
×
384

385
                // Get Response body
386
                len := pactffi_sync_message_get_response_contents_length(message, size_t(i))
48✔
387
                // if len == 0 {
48✔
388
                //         return nil, errors.New("retrieved an empty message")
48✔
389
                // }
48✔
390
                if len == 0 {
72✔
391
                        // You can have empty bodies
24✔
392
                        log.Println("[DEBUG] message body is empty")
24✔
393
                        responses[i] = []byte{}
24✔
394
                        return responses, nil
24✔
395
                }
24✔
396
                data := pactffi_sync_message_get_response_contents_bin(message, size_t(i))
24✔
397
                if unsafe.Pointer(data) == nil {
24✔
NEW
398
                        return nil, errors.New("retrieved an empty pointer to the message contents")
×
NEW
399
                }
×
400
                bytes := GoByteArrayFromC(data, int(len))
24✔
401
                responses[i] = bytes
24✔
402
        }
403

404
        return responses, nil
24✔
405
}
406

407
// StartTransport starts up a mock server on the given address:port for the given transport
408
// https://docs.rs/pact_ffi/latest/pact_ffi/mock_server/fn.pactffi_create_mock_server_for_transport.html
409
func (m *MessageServer) StartTransport(transport string, address string, port int, config map[string][]interface{}) (int, error) {
24✔
410
        if len(m.messages) == 0 {
24✔
411
                return 0, ErrNoInteractions
×
412
        }
×
413

414
        log.Println("[DEBUG] mock server starting on address:", address, port)
24✔
415
        configJson := stringFromInterface(config)
24✔
416

24✔
417
        p := pactffi_create_mock_server_for_transport(m.messagePact.handle, address, uint16(port), transport, configJson)
24✔
418

24✔
419
        // | Error | Description
24✔
420
        // |-------|-------------
24✔
421
        // | -1           | An invalid handle was received. Handles should be created with pactffi_new_pact
24✔
422
        // | -2           | transport_config is not valid JSON
24✔
423
        // | -3           | The mock server could not be started
24✔
424
        // | -4           | The method panicked
24✔
425
        // | -5           | The address is not valid
24✔
426
        msPort := int(p)
24✔
427
        switch msPort {
24✔
428
        case -1:
×
429
                return 0, ErrInvalidMockServerConfig
×
430
        case -2:
×
431
                return 0, ErrInvalidMockServerConfig
×
432
        case -3:
×
433
                return 0, ErrMockServerUnableToStart
×
434
        case -4:
×
435
                return 0, ErrMockServerPanic
×
436
        case -5:
×
437
                return 0, ErrInvalidAddress
×
438
        default:
24✔
439
                if msPort > 0 {
48✔
440
                        log.Println("[DEBUG] mock server running on port:", msPort)
24✔
441
                        return msPort, nil
24✔
442
                }
24✔
443
                return msPort, fmt.Errorf("an unknown error (code: %v) occurred when starting a mock server for the test", msPort)
×
444
        }
445
}
446

447
// NewInteraction initialises a new interaction for the current contract
448
func (m *MessageServer) CleanupPlugins() {
60✔
449
        pactffi_cleanup_plugins(m.messagePact.handle)
60✔
450
}
60✔
451

452
// CleanupMockServer frees the memory from the previous mock server.
453
func (m *MessageServer) CleanupMockServer(port int) bool {
24✔
454
        if len(m.messages) == 0 {
24✔
455
                return true
×
456
        }
×
457
        log.Println("[DEBUG] mock server cleaning up port:", port)
24✔
458
        res := pactffi_cleanup_mock_server(int32(port))
24✔
459

24✔
460
        return res
24✔
461
}
462

463
// MockServerMismatchedRequests returns a JSON object containing any mismatches from
464
// the last set of interactions.
465
func (m *MessageServer) MockServerMismatchedRequests(port int) []MismatchedRequest {
24✔
466
        log.Println("[DEBUG] mock server determining mismatches:", port)
24✔
467
        var res []MismatchedRequest
24✔
468

24✔
469
        mismatches := pactffi_mock_server_mismatches(int32(port))
24✔
470
        // This method can return a nil pointer, in which case, it
24✔
471
        // should be considered a failure (or at least, an issue)
24✔
472
        // converting it to a string might also do nasty things here!
24✔
473
        // TODO change return type to uintptr
24✔
474
        if mismatches == "" {
24✔
475
                log.Println("[WARN] received a null pointer from the native interface, returning empty list of mismatches")
×
476
                return []MismatchedRequest{}
×
477
        }
×
478

479
        err := json.Unmarshal([]byte(mismatches), &res)
24✔
480
        if err != nil {
24✔
481
                log.Println("[ERROR] failed to unmarshal mismatches response, returning empty list of mismatches")
×
482
                return []MismatchedRequest{}
×
483
        }
×
484

485
        return res
24✔
486
}
487

488
// MockServerMismatchedRequests returns a JSON object containing any mismatches from
489
// the last set of interactions.
490
func (m *MessageServer) MockServerMatched(port int) bool {
×
491
        log.Println("[DEBUG] mock server determining mismatches:", port)
×
492

×
NEW
493
        res := pactffi_mock_server_matched(int32(port))
×
494

×
495
        // TODO: why this number is so big and not a bool? Type def wrong? Port value wrong?
×
496
        // log.Println("MATCHED RES?")
×
497
        // log.Println(int(res))
×
498

×
NEW
499
        return res
×
500
}
×
501

502
// WritePactFile writes the Pact to file.
503
func (m *MessageServer) WritePactFile(dir string, overwrite bool) error {
36✔
504
        log.Println("[DEBUG] writing pact file for message pact at dir:", dir)
36✔
505

36✔
506
        overwritePact := false
36✔
507
        if overwrite {
36✔
508
                overwritePact = true
×
509
        }
×
510

511
        res := pactffi_write_message_pact_file(m.messagePact.handle, dir, overwritePact)
36✔
512

36✔
513
        /// | Error | Description |
36✔
514
        /// |-------|-------------|
36✔
515
        /// | 1 | The pact file was not able to be written |
36✔
516
        /// | 2 | The message pact for the given handle was not found |
36✔
517
        switch res {
36✔
518
        case 0:
36✔
519
                return nil
36✔
520
        case 1:
×
521
                return ErrUnableToWritePactFile
×
522
        case 2:
×
523
                return ErrHandleNotFound
×
524
        default:
×
525
                return fmt.Errorf("an unknown error ocurred when writing to pact file")
×
526
        }
527
}
528

529
// WritePactFile writes the Pact to file.
530
func (m *MessageServer) WritePactFileForServer(port int, dir string, overwrite bool) error {
24✔
531
        log.Println("[DEBUG] writing pact file for message pact at dir:", dir)
24✔
532

24✔
533
        overwritePact := false
24✔
534
        if overwrite {
48✔
535
                overwritePact = true
24✔
536
        }
24✔
537

538
        res := pactffi_write_pact_file(int32(port), dir, overwritePact)
24✔
539

24✔
540
        /// | Error | Description |
24✔
541
        /// |-------|-------------|
24✔
542
        /// | 1 | The pact file was not able to be written |
24✔
543
        /// | 2 | The message pact for the given handle was not found |
24✔
544
        switch res {
24✔
545
        case 0:
24✔
546
                return nil
24✔
547
        case 1:
×
548
                return ErrMockServerPanic
×
549
        case 2:
×
550
                return ErrUnableToWritePactFile
×
551
        case 3:
×
552
                return ErrHandleNotFound
×
553
        default:
×
554
                return fmt.Errorf("an unknown error ocurred when writing to pact file")
×
555
        }
556
}
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