|
| 1 | +/* |
| 2 | +Copyright IBM Corp. All Rights Reserved. |
| 3 | +
|
| 4 | +SPDX-License-Identifier: Apache-2.0 |
| 5 | +*/ |
| 6 | + |
| 7 | +package config |
| 8 | + |
| 9 | +import ( |
| 10 | + "testing" |
| 11 | + "time" |
| 12 | + |
| 13 | + "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" |
| 14 | + "github.com/stretchr/testify/assert" |
| 15 | + "github.com/stretchr/testify/require" |
| 16 | +) |
| 17 | + |
| 18 | +// failingConfigService fails every UnmarshalKey, standing in for a malformed |
| 19 | +// token.selector block. |
| 20 | +type failingConfigService struct{} |
| 21 | + |
| 22 | +func (failingConfigService) UnmarshalKey(string, any) error { |
| 23 | + return errors.New("malformed token.selector block") |
| 24 | +} |
| 25 | + |
| 26 | +// TestNewReturnsUsableConfigOnError pins that New never hands back a nil |
| 27 | +// *Config. Every caller logs the error and carries on with defaults, so a nil |
| 28 | +// would panic on the first Get*/Validate call — i.e. a malformed |
| 29 | +// token.selector block would take the node down instead of falling back. |
| 30 | +func TestNewReturnsUsableConfigOnError(t *testing.T) { |
| 31 | + cfg, err := New(failingConfigService{}) |
| 32 | + require.Error(t, err) |
| 33 | + require.NotNil(t, cfg, "callers log the error and keep using cfg") |
| 34 | + |
| 35 | + // Every accessor must work and return the documented default. |
| 36 | + assert.NotPanics(t, func() { |
| 37 | + require.NoError(t, cfg.Validate()) |
| 38 | + assert.Equal(t, defaultDriver, cfg.GetDriver()) |
| 39 | + assert.Equal(t, defaultMaxTokensPerSelection, cfg.GetMaxTokensPerSelection()) |
| 40 | + assert.Equal(t, defaultMaxLockAttempts, cfg.GetMaxLockAttempts()) |
| 41 | + assert.Equal(t, defaultMaxLocksPerTransaction, cfg.GetMaxLocksPerTransaction()) |
| 42 | + assert.Equal(t, defaultMaxRetries, cfg.GetNumRetries()) |
| 43 | + assert.Equal(t, defaultRetryInterval, cfg.GetRetryInterval()) |
| 44 | + assert.Positive(t, cfg.GetSelectionTimeout()) |
| 45 | + }) |
| 46 | +} |
| 47 | + |
| 48 | +// TestGetLimitsDefaultTimeoutClearsRetryBudget pins the relationship between the |
| 49 | +// two defaults: the wall-clock timeout has to outlast the worst-case retry |
| 50 | +// budget, or contention that the retries would have resolved is reported as |
| 51 | +// SelectorTimedOut instead. |
| 52 | +func TestGetLimitsDefaultTimeoutClearsRetryBudget(t *testing.T) { |
| 53 | + for _, tc := range []struct { |
| 54 | + name string |
| 55 | + cfg *Config |
| 56 | + minimumBudget time.Duration |
| 57 | + }{ |
| 58 | + { |
| 59 | + name: "defaults", |
| 60 | + cfg: &Config{}, |
| 61 | + minimumBudget: time.Duration(defaultMaxRetries) * defaultRetryInterval, |
| 62 | + }, |
| 63 | + { |
| 64 | + name: "explicit retry interval", |
| 65 | + cfg: &Config{RetryInterval: 10 * time.Second}, |
| 66 | + minimumBudget: time.Duration(defaultMaxRetries) * 10 * time.Second, |
| 67 | + }, |
| 68 | + { |
| 69 | + name: "explicit retry count", |
| 70 | + cfg: &Config{Limits: Limits{MaxRetries: 25}}, |
| 71 | + minimumBudget: 25 * defaultRetryInterval, |
| 72 | + }, |
| 73 | + } { |
| 74 | + t.Run(tc.name, func(t *testing.T) { |
| 75 | + limits := tc.cfg.GetLimits() |
| 76 | + assert.Greater(t, limits.SelectionTimeout, tc.minimumBudget, |
| 77 | + "default timeout must outlast %d retries of up to %v each", |
| 78 | + limits.MaxRetries, tc.cfg.GetRetryInterval()) |
| 79 | + }) |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +// TestGetLimitsExplicitTimeoutIsHonoured verifies the derivation above only |
| 84 | +// fills in a missing value and never overrides an operator's own timeout. |
| 85 | +func TestGetLimitsExplicitTimeoutIsHonoured(t *testing.T) { |
| 86 | + cfg := &Config{Limits: Limits{SelectionTimeout: 3 * time.Second, MaxRetries: 100}} |
| 87 | + assert.Equal(t, 3*time.Second, cfg.GetLimits().SelectionTimeout) |
| 88 | +} |
0 commit comments