websearch_config_test.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. //go:build unit
  2. package service
  3. import (
  4. "context"
  5. "testing"
  6. "github.com/Wei-Shaw/sub2api/internal/pkg/websearch"
  7. "github.com/stretchr/testify/require"
  8. )
  9. // --- validateWebSearchConfig ---
  10. func TestValidateWebSearchConfig_Nil(t *testing.T) {
  11. require.NoError(t, validateWebSearchConfig(nil))
  12. }
  13. func TestValidateWebSearchConfig_Valid(t *testing.T) {
  14. cfg := &WebSearchEmulationConfig{
  15. Enabled: true,
  16. Providers: []WebSearchProviderConfig{
  17. {Type: "brave", QuotaLimit: int64Ptr(1000)},
  18. {Type: "tavily", QuotaLimit: int64Ptr(500)},
  19. },
  20. }
  21. require.NoError(t, validateWebSearchConfig(cfg))
  22. }
  23. func TestValidateWebSearchConfig_TooManyProviders(t *testing.T) {
  24. cfg := &WebSearchEmulationConfig{Providers: make([]WebSearchProviderConfig, 11)}
  25. for i := range cfg.Providers {
  26. cfg.Providers[i] = WebSearchProviderConfig{Type: "brave"}
  27. }
  28. err := validateWebSearchConfig(cfg)
  29. require.ErrorContains(t, err, "too many providers")
  30. }
  31. func TestValidateWebSearchConfig_InvalidType(t *testing.T) {
  32. cfg := &WebSearchEmulationConfig{
  33. Providers: []WebSearchProviderConfig{{Type: "bing"}},
  34. }
  35. require.ErrorContains(t, validateWebSearchConfig(cfg), "invalid type")
  36. }
  37. func TestValidateWebSearchConfig_NegativeQuotaLimit(t *testing.T) {
  38. cfg := &WebSearchEmulationConfig{
  39. Providers: []WebSearchProviderConfig{{Type: "brave", QuotaLimit: int64Ptr(-1)}},
  40. }
  41. require.ErrorContains(t, validateWebSearchConfig(cfg), "quota_limit must be > 0 or null")
  42. }
  43. func TestValidateWebSearchConfig_DuplicateType(t *testing.T) {
  44. cfg := &WebSearchEmulationConfig{
  45. Providers: []WebSearchProviderConfig{
  46. {Type: "brave"},
  47. {Type: "brave"},
  48. },
  49. }
  50. require.ErrorContains(t, validateWebSearchConfig(cfg), "duplicate type")
  51. }
  52. func TestValidateWebSearchConfig_NilQuotaLimit(t *testing.T) {
  53. cfg := &WebSearchEmulationConfig{
  54. Providers: []WebSearchProviderConfig{{Type: "brave", QuotaLimit: nil}},
  55. }
  56. require.NoError(t, validateWebSearchConfig(cfg))
  57. }
  58. // --- parseWebSearchConfigJSON ---
  59. func TestParseWebSearchConfigJSON_ValidJSON(t *testing.T) {
  60. raw := `{"enabled":true,"providers":[{"type":"brave","api_key":"sk-xxx"}]}`
  61. cfg := parseWebSearchConfigJSON(raw)
  62. require.True(t, cfg.Enabled)
  63. require.Len(t, cfg.Providers, 1)
  64. require.Equal(t, "brave", cfg.Providers[0].Type)
  65. }
  66. func TestParseWebSearchConfigJSON_EmptyString(t *testing.T) {
  67. cfg := parseWebSearchConfigJSON("")
  68. require.False(t, cfg.Enabled)
  69. require.Empty(t, cfg.Providers)
  70. }
  71. func TestParseWebSearchConfigJSON_InvalidJSON(t *testing.T) {
  72. cfg := parseWebSearchConfigJSON("not{json")
  73. require.False(t, cfg.Enabled)
  74. require.Empty(t, cfg.Providers)
  75. }
  76. func TestParseWebSearchConfigJSON_BackwardCompatibility(t *testing.T) {
  77. // Old config with priority and quota_refresh_interval should parse without error
  78. raw := `{"enabled":true,"providers":[{"type":"brave","priority":1,"quota_refresh_interval":"monthly","quota_limit":1000}]}`
  79. cfg := parseWebSearchConfigJSON(raw)
  80. require.True(t, cfg.Enabled)
  81. require.Len(t, cfg.Providers, 1)
  82. require.Equal(t, int64(1000), *cfg.Providers[0].QuotaLimit)
  83. }
  84. // --- SanitizeWebSearchConfig ---
  85. func TestSanitizeWebSearchConfig_MaskAPIKey(t *testing.T) {
  86. cfg := &WebSearchEmulationConfig{
  87. Enabled: true,
  88. Providers: []WebSearchProviderConfig{
  89. {Type: "brave", APIKey: "sk-secret-xxx"},
  90. },
  91. }
  92. out := SanitizeWebSearchConfig(context.Background(), cfg)
  93. require.Equal(t, "", out.Providers[0].APIKey)
  94. require.True(t, out.Providers[0].APIKeyConfigured)
  95. }
  96. func TestSanitizeWebSearchConfig_NoAPIKey(t *testing.T) {
  97. cfg := &WebSearchEmulationConfig{
  98. Providers: []WebSearchProviderConfig{{Type: "brave", APIKey: ""}},
  99. }
  100. out := SanitizeWebSearchConfig(context.Background(), cfg)
  101. require.Equal(t, "", out.Providers[0].APIKey)
  102. require.False(t, out.Providers[0].APIKeyConfigured)
  103. }
  104. func TestSanitizeWebSearchConfig_Nil(t *testing.T) {
  105. require.Nil(t, SanitizeWebSearchConfig(context.Background(), nil))
  106. }
  107. func TestSanitizeWebSearchConfig_PreservesOtherFields(t *testing.T) {
  108. cfg := &WebSearchEmulationConfig{
  109. Enabled: true,
  110. Providers: []WebSearchProviderConfig{
  111. {Type: "brave", APIKey: "secret", QuotaLimit: int64Ptr(1000)},
  112. },
  113. }
  114. out := SanitizeWebSearchConfig(context.Background(), cfg)
  115. require.True(t, out.Enabled)
  116. require.Equal(t, int64(1000), *out.Providers[0].QuotaLimit)
  117. }
  118. func TestSanitizeWebSearchConfig_DoesNotMutateOriginal(t *testing.T) {
  119. cfg := &WebSearchEmulationConfig{
  120. Providers: []WebSearchProviderConfig{{Type: "brave", APIKey: "secret"}},
  121. }
  122. _ = SanitizeWebSearchConfig(context.Background(), cfg)
  123. require.Equal(t, "secret", cfg.Providers[0].APIKey)
  124. }
  125. // --- PopulateWebSearchUsage ---
  126. func TestPopulateWebSearchUsage_NilInput(t *testing.T) {
  127. require.Nil(t, PopulateWebSearchUsage(context.Background(), nil))
  128. }
  129. func TestPopulateWebSearchUsage_NoManager_QuotaUsedZero(t *testing.T) {
  130. // Ensure no global manager is set
  131. SetWebSearchManager(nil)
  132. defer SetWebSearchManager(nil)
  133. cfg := &WebSearchEmulationConfig{
  134. Enabled: true,
  135. Providers: []WebSearchProviderConfig{
  136. {Type: "brave", APIKey: "sk-key", QuotaLimit: int64Ptr(1000)},
  137. },
  138. }
  139. out := PopulateWebSearchUsage(context.Background(), cfg)
  140. require.NotNil(t, out)
  141. require.Len(t, out.Providers, 1)
  142. require.Equal(t, int64(0), out.Providers[0].QuotaUsed)
  143. }
  144. func TestPopulateWebSearchUsage_APIKeyConfigured_True(t *testing.T) {
  145. SetWebSearchManager(nil)
  146. defer SetWebSearchManager(nil)
  147. cfg := &WebSearchEmulationConfig{
  148. Providers: []WebSearchProviderConfig{
  149. {Type: "brave", APIKey: "sk-key"},
  150. },
  151. }
  152. out := PopulateWebSearchUsage(context.Background(), cfg)
  153. require.True(t, out.Providers[0].APIKeyConfigured)
  154. }
  155. func TestPopulateWebSearchUsage_APIKeyConfigured_False(t *testing.T) {
  156. SetWebSearchManager(nil)
  157. defer SetWebSearchManager(nil)
  158. cfg := &WebSearchEmulationConfig{
  159. Providers: []WebSearchProviderConfig{
  160. {Type: "brave", APIKey: ""},
  161. },
  162. }
  163. out := PopulateWebSearchUsage(context.Background(), cfg)
  164. require.False(t, out.Providers[0].APIKeyConfigured)
  165. }
  166. func TestPopulateWebSearchUsage_NilQuotaLimit(t *testing.T) {
  167. SetWebSearchManager(nil)
  168. defer SetWebSearchManager(nil)
  169. cfg := &WebSearchEmulationConfig{
  170. Providers: []WebSearchProviderConfig{
  171. {Type: "brave", APIKey: "sk-key", QuotaLimit: nil},
  172. },
  173. }
  174. out := PopulateWebSearchUsage(context.Background(), cfg)
  175. require.Nil(t, out.Providers[0].QuotaLimit)
  176. }
  177. func TestPopulateWebSearchUsage_NonNilQuotaLimit(t *testing.T) {
  178. SetWebSearchManager(nil)
  179. defer SetWebSearchManager(nil)
  180. cfg := &WebSearchEmulationConfig{
  181. Providers: []WebSearchProviderConfig{
  182. {Type: "brave", APIKey: "sk-key", QuotaLimit: int64Ptr(500)},
  183. },
  184. }
  185. out := PopulateWebSearchUsage(context.Background(), cfg)
  186. require.NotNil(t, out.Providers[0].QuotaLimit)
  187. require.Equal(t, int64(500), *out.Providers[0].QuotaLimit)
  188. }
  189. func TestPopulateWebSearchUsage_WithManager_NilRedis(t *testing.T) {
  190. // Manager with nil Redis returns 0 usage without error
  191. mgr := websearch.NewManager([]websearch.ProviderConfig{
  192. {Type: "brave", APIKey: "k"},
  193. }, nil)
  194. SetWebSearchManager(mgr)
  195. defer SetWebSearchManager(nil)
  196. cfg := &WebSearchEmulationConfig{
  197. Providers: []WebSearchProviderConfig{
  198. {Type: "brave", APIKey: "sk-key", QuotaLimit: int64Ptr(1000)},
  199. },
  200. }
  201. out := PopulateWebSearchUsage(context.Background(), cfg)
  202. require.Equal(t, int64(0), out.Providers[0].QuotaUsed)
  203. require.True(t, out.Providers[0].APIKeyConfigured)
  204. }
  205. func TestPopulateWebSearchUsage_DoesNotMutateOriginal(t *testing.T) {
  206. SetWebSearchManager(nil)
  207. defer SetWebSearchManager(nil)
  208. cfg := &WebSearchEmulationConfig{
  209. Providers: []WebSearchProviderConfig{
  210. {Type: "brave", APIKey: "secret", QuotaLimit: int64Ptr(100)},
  211. },
  212. }
  213. _ = PopulateWebSearchUsage(context.Background(), cfg)
  214. // Original should be unchanged
  215. require.Equal(t, "secret", cfg.Providers[0].APIKey)
  216. require.Equal(t, int64(0), cfg.Providers[0].QuotaUsed)
  217. }
  218. // --- ResetWebSearchUsage ---
  219. func TestResetWebSearchUsage_NilManager(t *testing.T) {
  220. SetWebSearchManager(nil)
  221. defer SetWebSearchManager(nil)
  222. err := ResetWebSearchUsage(context.Background(), "brave")
  223. require.Error(t, err)
  224. require.Contains(t, err.Error(), "not initialized")
  225. }