selector_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. package features
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "testing"
  7. "time"
  8. "github.com/rs/zerolog"
  9. "github.com/stretchr/testify/require"
  10. )
  11. func TestUnmarshalFeaturesRecord(t *testing.T) {
  12. tests := []struct {
  13. record []byte
  14. expectedPercentage int32
  15. }{
  16. {
  17. record: []byte(`{"dv3":0}`),
  18. expectedPercentage: 0,
  19. },
  20. {
  21. record: []byte(`{"dv3":39}`),
  22. expectedPercentage: 39,
  23. },
  24. {
  25. record: []byte(`{"dv3":100}`),
  26. expectedPercentage: 100,
  27. },
  28. {
  29. record: []byte(`{}`), // Unmarshal to default struct if key is not present
  30. },
  31. {
  32. record: []byte(`{"kyber":768}`), // Unmarshal to default struct if key is not present
  33. },
  34. }
  35. for _, test := range tests {
  36. var features featuresRecord
  37. err := json.Unmarshal(test.record, &features)
  38. require.NoError(t, err)
  39. require.Equal(t, test.expectedPercentage, features.DatagramV3Percentage, test)
  40. }
  41. }
  42. func TestFeaturePrecedenceEvaluationPostQuantum(t *testing.T) {
  43. logger := zerolog.Nop()
  44. tests := []struct {
  45. name string
  46. cli bool
  47. expectedFeatures []string
  48. expectedVersion PostQuantumMode
  49. }{
  50. {
  51. name: "default",
  52. cli: false,
  53. expectedFeatures: defaultFeatures,
  54. expectedVersion: PostQuantumPrefer,
  55. },
  56. {
  57. name: "user_specified",
  58. cli: true,
  59. expectedFeatures: Dedup(append(defaultFeatures, FeaturePostQuantum)),
  60. expectedVersion: PostQuantumStrict,
  61. },
  62. }
  63. for _, test := range tests {
  64. t.Run(test.name, func(t *testing.T) {
  65. resolver := &staticResolver{record: featuresRecord{}}
  66. selector, err := newFeatureSelector(context.Background(), test.name, &logger, resolver, []string{}, test.cli, time.Second)
  67. require.NoError(t, err)
  68. require.ElementsMatch(t, test.expectedFeatures, selector.ClientFeatures())
  69. require.Equal(t, test.expectedVersion, selector.PostQuantumMode())
  70. })
  71. }
  72. }
  73. func TestFeaturePrecedenceEvaluationDatagramVersion(t *testing.T) {
  74. logger := zerolog.Nop()
  75. tests := []struct {
  76. name string
  77. cli []string
  78. remote featuresRecord
  79. expectedFeatures []string
  80. expectedVersion DatagramVersion
  81. }{
  82. {
  83. name: "default",
  84. cli: []string{},
  85. remote: featuresRecord{},
  86. expectedFeatures: defaultFeatures,
  87. expectedVersion: DatagramV2,
  88. },
  89. {
  90. name: "user_specified_v2",
  91. cli: []string{FeatureDatagramV2},
  92. remote: featuresRecord{},
  93. expectedFeatures: defaultFeatures,
  94. expectedVersion: DatagramV2,
  95. },
  96. {
  97. name: "user_specified_v3",
  98. cli: []string{FeatureDatagramV3},
  99. remote: featuresRecord{},
  100. expectedFeatures: Dedup(append(defaultFeatures, FeatureDatagramV3)),
  101. expectedVersion: FeatureDatagramV3,
  102. },
  103. {
  104. name: "remote_specified_v3",
  105. cli: []string{},
  106. remote: featuresRecord{
  107. DatagramV3Percentage: 100,
  108. },
  109. expectedFeatures: Dedup(append(defaultFeatures, FeatureDatagramV3)),
  110. expectedVersion: FeatureDatagramV3,
  111. },
  112. {
  113. name: "remote_and_user_specified_v3",
  114. cli: []string{FeatureDatagramV3},
  115. remote: featuresRecord{
  116. DatagramV3Percentage: 100,
  117. },
  118. expectedFeatures: Dedup(append(defaultFeatures, FeatureDatagramV3)),
  119. expectedVersion: FeatureDatagramV3,
  120. },
  121. {
  122. name: "remote_v3_and_user_specified_v2",
  123. cli: []string{FeatureDatagramV2},
  124. remote: featuresRecord{
  125. DatagramV3Percentage: 100,
  126. },
  127. expectedFeatures: defaultFeatures,
  128. expectedVersion: DatagramV2,
  129. },
  130. }
  131. for _, test := range tests {
  132. t.Run(test.name, func(t *testing.T) {
  133. resolver := &staticResolver{record: test.remote}
  134. selector, err := newFeatureSelector(context.Background(), test.name, &logger, resolver, test.cli, false, time.Second)
  135. require.NoError(t, err)
  136. require.ElementsMatch(t, test.expectedFeatures, selector.ClientFeatures())
  137. require.Equal(t, test.expectedVersion, selector.DatagramVersion())
  138. })
  139. }
  140. }
  141. func TestRefreshFeaturesRecord(t *testing.T) {
  142. // The hash of the accountTag is 82
  143. accountTag := t.Name()
  144. threshold := switchThreshold(accountTag)
  145. percentages := []int32{0, 10, 81, 82, 83, 100, 101, 1000}
  146. refreshFreq := time.Millisecond * 10
  147. selector := newTestSelector(t, percentages, false, refreshFreq)
  148. // Starting out should default to DatagramV2
  149. require.Equal(t, DatagramV2, selector.DatagramVersion())
  150. for _, percentage := range percentages {
  151. if percentage > threshold {
  152. require.Equal(t, DatagramV3, selector.DatagramVersion())
  153. } else {
  154. require.Equal(t, DatagramV2, selector.DatagramVersion())
  155. }
  156. time.Sleep(refreshFreq + time.Millisecond)
  157. }
  158. // Make sure error doesn't override the last fetched features
  159. require.Equal(t, DatagramV3, selector.DatagramVersion())
  160. }
  161. func TestStaticFeatures(t *testing.T) {
  162. percentages := []int32{0}
  163. // PostQuantum Enabled from user flag
  164. selector := newTestSelector(t, percentages, true, time.Millisecond*10)
  165. require.Equal(t, PostQuantumStrict, selector.PostQuantumMode())
  166. // PostQuantum Disabled (or not set)
  167. selector = newTestSelector(t, percentages, false, time.Millisecond*10)
  168. require.Equal(t, PostQuantumPrefer, selector.PostQuantumMode())
  169. }
  170. func newTestSelector(t *testing.T, percentages []int32, pq bool, refreshFreq time.Duration) *FeatureSelector {
  171. accountTag := t.Name()
  172. logger := zerolog.Nop()
  173. resolver := &mockResolver{
  174. percentages: percentages,
  175. }
  176. selector, err := newFeatureSelector(context.Background(), accountTag, &logger, resolver, []string{}, pq, refreshFreq)
  177. require.NoError(t, err)
  178. return selector
  179. }
  180. type mockResolver struct {
  181. nextIndex int
  182. percentages []int32
  183. }
  184. func (mr *mockResolver) lookupRecord(ctx context.Context) ([]byte, error) {
  185. if mr.nextIndex >= len(mr.percentages) {
  186. return nil, fmt.Errorf("no more record to lookup")
  187. }
  188. record, err := json.Marshal(featuresRecord{
  189. DatagramV3Percentage: mr.percentages[mr.nextIndex],
  190. })
  191. mr.nextIndex++
  192. return record, err
  193. }
  194. type staticResolver struct {
  195. record featuresRecord
  196. }
  197. func (r *staticResolver) lookupRecord(ctx context.Context) ([]byte, error) {
  198. return json.Marshal(r.record)
  199. }