1package ports
2
3import (
4 "errors"
5 "fmt"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9)
10
11// TestBrokerStoreOutcomeUnknownErrorClassification pins the indeterminate-write
12// contract at the ports seam: the error reports a store failure that happened
13// after the durable commit point, stays classifiable after wrapping, and keeps
14// the underlying store cause inspectable so a caller can log or classify it
15// without importing the adapter.
16func TestBrokerStoreOutcomeUnknownErrorClassification(t *testing.T) {
17 cause := errors.New("power loss")
18 unknown := BrokerStoreOutcomeUnknownError{Err: cause}
19
20 require.ErrorContains(t, unknown, "outcome unknown")
21 require.ErrorContains(t, unknown, "power loss")
22 require.ErrorIs(t, unknown, cause, "the underlying store cause stays inspectable")
23 var classified BrokerStoreOutcomeUnknownError
24 require.ErrorAs(t, unknown, &classified)
25 require.Equal(t, cause, classified.Err)
26
27 // Wrapping by a caller or a composing use case must not erase the
28 // classification: that is the whole point of a typed error here.
29 wrapped := fmt.Errorf("broker: replace hosts: %w", unknown)
30 require.ErrorAs(t, wrapped, &classified)
31 require.ErrorIs(t, wrapped, cause)
32
33 // A definite store failure is never an unknown outcome, so a caller cannot
34 // mistake a retryable conflict for a reopen-required indeterminate write.
35 for name, definite := range map[string]error{
36 "conflict": ErrBrokerHostConflict,
37 "locked": ErrBrokerStoreLocked,
38 "invalid state": ErrBrokerStoreInvalidState,
39 "immutable": ErrBrokerMembershipImmutable,
40 } {
41 t.Run(name, func(t *testing.T) {
42 require.False(t, errors.As(definite, &classified))
43 require.False(t, errors.As(fmt.Errorf("broker: replace hosts: %w", definite), &classified))
44 })
45 }
46}