statuserr_test.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package ctrlutil
  14. import (
  15. "errors"
  16. "fmt"
  17. "strings"
  18. "testing"
  19. )
  20. // providerErr stands in for an error returned by provider code, which may embed
  21. // secret material and must never reach a status condition.
  22. const providerErr = "json: cannot unmarshal number 8019210420527506405 into Go value of type float64"
  23. func TestSafeMessage(t *testing.T) {
  24. sentinel := errors.New("secret is immutable")
  25. tests := []struct {
  26. name string
  27. err error
  28. want string
  29. }{
  30. {
  31. name: "nil error yields nothing",
  32. err: nil,
  33. want: "",
  34. },
  35. {
  36. name: "unmarked error yields nothing",
  37. err: errors.New(providerErr),
  38. want: "",
  39. },
  40. {
  41. name: "marked error is published",
  42. err: Safe(errors.New("could not update secret foo: already exists")),
  43. want: "could not update secret foo: already exists",
  44. },
  45. {
  46. name: "marking nil stays nil",
  47. err: Safe(nil),
  48. want: "",
  49. },
  50. {
  51. name: "wrapping a marked error keeps it publishable",
  52. err: fmt.Errorf("could not update secret: %w", Safe(sentinel)),
  53. want: "secret is immutable",
  54. },
  55. {
  56. name: "marked twice is not duplicated",
  57. err: Safe(Safe(errors.New("target is owned by another ExternalSecret"))),
  58. want: "target is owned by another ExternalSecret",
  59. },
  60. }
  61. for _, tt := range tests {
  62. t.Run(tt.name, func(t *testing.T) {
  63. if got := SafeMessage(tt.err); got != tt.want {
  64. t.Errorf("SafeMessage() = %q, want %q", got, tt.want)
  65. }
  66. })
  67. }
  68. }
  69. // Text composed around a marked error must never be published, whichever way it
  70. // was composed. Each shape here leaked before SafeMessage took the innermost mark.
  71. func TestSafeMessageDoesNotPublishWrapperText(t *testing.T) {
  72. marked := Safe(errors.New("connection refused"))
  73. tests := []struct {
  74. name string
  75. err error
  76. }{
  77. {
  78. name: "unmarked provider wrapper",
  79. err: fmt.Errorf("%s: %w", providerErr, marked),
  80. },
  81. {
  82. name: "marked provider wrapper",
  83. err: Safe(fmt.Errorf("%s: %w", providerErr, marked)),
  84. },
  85. {
  86. name: "joined with a provider error",
  87. err: Safe(errors.Join(errors.New(providerErr), marked)),
  88. },
  89. }
  90. for _, tt := range tests {
  91. t.Run(tt.name, func(t *testing.T) {
  92. got := SafeMessage(tt.err)
  93. if got != "connection refused" {
  94. t.Errorf("SafeMessage() = %q, want %q", got, "connection refused")
  95. }
  96. if strings.Contains(got, "8019210420527506405") {
  97. t.Errorf("SafeMessage() leaked wrapper text: %q", got)
  98. }
  99. })
  100. }
  101. }
  102. func TestSafePreservesErrorsIs(t *testing.T) {
  103. sentinel := errors.New("sentinel")
  104. if !errors.Is(Safe(sentinel), sentinel) {
  105. t.Error("Safe() broke errors.Is against the wrapped error")
  106. }
  107. if Safe(sentinel).Error() != "sentinel" {
  108. t.Errorf("Safe().Error() = %q, want %q", Safe(sentinel).Error(), "sentinel")
  109. }
  110. }
  111. func TestSafeMessageTruncates(t *testing.T) {
  112. long := strings.Repeat("a", MaxConditionMessageLength+50)
  113. got := SafeMessage(Safe(errors.New(long)))
  114. want := strings.Repeat("a", MaxConditionMessageLength-len("...")) + "..."
  115. if got != want {
  116. t.Errorf("SafeMessage() length = %d, want %d", len(got), len(want))
  117. }
  118. }
  119. // Truncation counts runes, so a multi-byte message is not cut mid-character.
  120. func TestSafeMessageTruncatesOnRunes(t *testing.T) {
  121. long := strings.Repeat("é", MaxConditionMessageLength+10)
  122. got := SafeMessage(Safe(errors.New(long)))
  123. if runes := []rune(got); len(runes) != MaxConditionMessageLength {
  124. t.Errorf("truncated to %d runes, want %d", len(runes), MaxConditionMessageLength)
  125. }
  126. if !strings.HasPrefix(got, "é") {
  127. t.Errorf("truncation split a multi-byte rune: %q", got[:8])
  128. }
  129. }
  130. // A limit too small to hold the marker still has to be respected.
  131. func TestTruncateLimitBelowMarker(t *testing.T) {
  132. if got := truncate("abcdef", 2); got != "ab" {
  133. t.Errorf("truncate() = %q, want %q", got, "ab")
  134. }
  135. }