setting_handler_partial_payload_test.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. //go:build unit
  2. package admin
  3. import (
  4. "net/http"
  5. "testing"
  6. "github.com/Wei-Shaw/sub2api/internal/service"
  7. "github.com/stretchr/testify/require"
  8. )
  9. // Saving settings is a whole-document PUT. A client that sends only the field it
  10. // cares about must not reset everything else: a payload as small as
  11. // `{"risk_control_enabled":true}` used to clear site_name, after which
  12. // getStringOrDefault rendered the empty value as the built-in default and the
  13. // login page silently changed name.
  14. func TestUpdateSettingsPartialPayloadKeepsUnsentKeys(t *testing.T) {
  15. h, repo := newStepUpSwitchTestHandler(t, map[string]string{
  16. service.SettingKeySiteName: "Example Gateway",
  17. service.SettingKeySiteSubtitle: "Example Gateway Platform",
  18. service.SettingKeySMTPHost: "smtp.example.com",
  19. service.SettingKeySMTPFrom: "noreply@example.com",
  20. service.SettingKeyTurnstileEnabled: "true",
  21. })
  22. rec := doUpdateSettings(t, h, map[string]any{"risk_control_enabled": true}, nil)
  23. require.Equal(t, http.StatusOK, rec.Code)
  24. require.Equal(t, "true", repo.values[service.SettingKeyRiskControlEnabled],
  25. "the field the caller actually sent must be written")
  26. require.Equal(t, "Example Gateway", repo.values[service.SettingKeySiteName])
  27. require.Equal(t, "Example Gateway Platform", repo.values[service.SettingKeySiteSubtitle])
  28. require.Equal(t, "smtp.example.com", repo.values[service.SettingKeySMTPHost])
  29. require.Equal(t, "noreply@example.com", repo.values[service.SettingKeySMTPFrom])
  30. require.Equal(t, "true", repo.values[service.SettingKeyTurnstileEnabled])
  31. }
  32. // A full payload keeps whole-document semantics: fields explicitly set to their
  33. // zero value are still cleared.
  34. func TestUpdateSettingsFullPayloadStillClearsSentEmptyFields(t *testing.T) {
  35. h, repo := newStepUpSwitchTestHandler(t, map[string]string{
  36. service.SettingKeySiteName: "Example Gateway",
  37. })
  38. rec := doUpdateSettings(t, h, map[string]any{"site_name": ""}, nil)
  39. require.Equal(t, http.StatusOK, rec.Code)
  40. require.Equal(t, "", repo.values[service.SettingKeySiteName],
  41. "an explicitly sent empty value is a deliberate clear, not an omission")
  42. }
  43. // smtp_from_email is the one request field whose JSON name differs from its
  44. // setting key; the alias keeps it from being treated as always-omitted.
  45. func TestUpdateSettingsSMTPFromAliasIsWritable(t *testing.T) {
  46. h, repo := newStepUpSwitchTestHandler(t, map[string]string{
  47. service.SettingKeySMTPFrom: "old@example.com",
  48. })
  49. rec := doUpdateSettings(t, h, map[string]any{"smtp_from_email": "new@example.com"}, nil)
  50. require.Equal(t, http.StatusOK, rec.Code)
  51. require.Equal(t, "new@example.com", repo.values[service.SettingKeySMTPFrom])
  52. }
  53. func TestUpdateSettingsGrokDefaultBaseURLModeIsWritable(t *testing.T) {
  54. h, repo := newStepUpSwitchTestHandler(t, map[string]string{
  55. service.SettingKeyGrokDefaultBaseURLMode: service.GrokDefaultBaseURLModeCLI,
  56. })
  57. rec := doUpdateSettings(t, h, map[string]any{
  58. "grok_default_base_url_mode": service.GrokDefaultBaseURLModeEUWest1,
  59. }, nil)
  60. require.Equal(t, http.StatusOK, rec.Code)
  61. require.Equal(t, service.GrokDefaultBaseURLModeEUWest1, repo.values[service.SettingKeyGrokDefaultBaseURLMode])
  62. }
  63. func TestUpdateSettingsRejectsTwoCaptchaProviders(t *testing.T) {
  64. h, _ := newStepUpSwitchTestHandler(t, map[string]string{
  65. service.SettingKeyTurnstileEnabled: "true",
  66. service.SettingKeyTurnstileSiteKey: "site-key",
  67. service.SettingKeyTurnstileSecretKey: "turnstile-secret",
  68. })
  69. rec := doUpdateSettings(t, h, map[string]any{
  70. "turnstile_enabled": true,
  71. "turnstile_site_key": "site-key",
  72. "turnstile_secret_key": "turnstile-secret",
  73. "tencent_captcha_enabled": true,
  74. "tencent_captcha_app_id": "123456789",
  75. "tencent_captcha_app_secret_key": "app-secret",
  76. "tencent_captcha_cloud_secret_id": "cloud-secret-id",
  77. "tencent_captcha_cloud_secret_key": "cloud-secret-key",
  78. }, nil)
  79. require.Equal(t, http.StatusBadRequest, rec.Code)
  80. require.Contains(t, rec.Body.String(), "cannot be enabled at the same time")
  81. }
  82. func TestUpdateSettingsRequiresFourTencentCaptchaCredentialsWhenEnabled(t *testing.T) {
  83. h, _ := newStepUpSwitchTestHandler(t, map[string]string{})
  84. rec := doUpdateSettings(t, h, map[string]any{
  85. "tencent_captcha_enabled": true,
  86. "tencent_captcha_app_id": "123456789",
  87. }, nil)
  88. require.Equal(t, http.StatusBadRequest, rec.Code)
  89. require.Contains(t, rec.Body.String(), "AppSecretKey")
  90. }
  91. func TestUpdateSettingsRetainsStoredTencentCaptchaCredentialsWhenInputsEmpty(t *testing.T) {
  92. h, repo := newStepUpSwitchTestHandler(t, map[string]string{
  93. service.SettingKeyTencentCaptchaAppSecretKey: "stored-app-secret",
  94. service.SettingKeyTencentCaptchaCloudSecretID: "stored-cloud-secret-id",
  95. service.SettingKeyTencentCaptchaCloudSecretKey: "stored-cloud-secret-key",
  96. })
  97. rec := doUpdateSettings(t, h, map[string]any{
  98. "tencent_captcha_enabled": true,
  99. "tencent_captcha_app_id": "123456789",
  100. "tencent_captcha_app_secret_key": "",
  101. "tencent_captcha_cloud_secret_id": "",
  102. "tencent_captcha_cloud_secret_key": "",
  103. }, nil)
  104. require.Equal(t, http.StatusOK, rec.Code)
  105. require.Equal(t, "stored-app-secret", repo.values[service.SettingKeyTencentCaptchaAppSecretKey])
  106. require.Equal(t, "stored-cloud-secret-id", repo.values[service.SettingKeyTencentCaptchaCloudSecretID])
  107. require.Equal(t, "stored-cloud-secret-key", repo.values[service.SettingKeyTencentCaptchaCloudSecretKey])
  108. }
  109. // 天御站点决定前端加载哪个 SDK 与服务端打哪个接入点,两端必须一致。
  110. // 部分载荷把它重置回中国站,会让已配国际站的部署在下一次任意保存后整体失效。
  111. func TestUpdateSettingsPartialPayloadKeepsTencentCaptchaRegion(t *testing.T) {
  112. h, repo := newStepUpSwitchTestHandler(t, map[string]string{
  113. service.SettingKeyTencentCaptchaRegion: service.TencentCaptchaRegionINTL,
  114. })
  115. rec := doUpdateSettings(t, h, map[string]any{"risk_control_enabled": true}, nil)
  116. require.Equal(t, http.StatusOK, rec.Code)
  117. require.Equal(t, service.TencentCaptchaRegionINTL,
  118. repo.values[service.SettingKeyTencentCaptchaRegion])
  119. }
  120. func TestUpdateSettingsNormalizesUnknownTencentCaptchaRegion(t *testing.T) {
  121. h, repo := newStepUpSwitchTestHandler(t, map[string]string{
  122. service.SettingKeyTencentCaptchaRegion: service.TencentCaptchaRegionINTL,
  123. })
  124. rec := doUpdateSettings(t, h, map[string]any{"tencent_captcha_region": "sgp"}, nil)
  125. require.Equal(t, http.StatusOK, rec.Code)
  126. require.Equal(t, service.TencentCaptchaRegionCN,
  127. repo.values[service.SettingKeyTencentCaptchaRegion],
  128. "未知站点必须落回中国站,不能写入无法识别的值")
  129. }
  130. func TestUpdateSettingsWritesTencentCaptchaRegionWhenSent(t *testing.T) {
  131. h, repo := newStepUpSwitchTestHandler(t, map[string]string{})
  132. rec := doUpdateSettings(t, h, map[string]any{"tencent_captcha_region": "intl"}, nil)
  133. require.Equal(t, http.StatusOK, rec.Code)
  134. require.Equal(t, service.TencentCaptchaRegionINTL,
  135. repo.values[service.SettingKeyTencentCaptchaRegion])
  136. }
  137. func TestUpdateSettingsValidatesTencentCaptchaAppIDWhenEnabledFlagIsOmitted(t *testing.T) {
  138. h, _ := newStepUpSwitchTestHandler(t, map[string]string{
  139. service.SettingKeyTencentCaptchaEnabled: "true",
  140. service.SettingKeyTencentCaptchaAppID: "123456789",
  141. service.SettingKeyTencentCaptchaAppSecretKey: "stored-app-secret",
  142. service.SettingKeyTencentCaptchaCloudSecretID: "stored-cloud-secret-id",
  143. service.SettingKeyTencentCaptchaCloudSecretKey: "stored-cloud-secret-key",
  144. })
  145. rec := doUpdateSettings(t, h, map[string]any{
  146. "tencent_captcha_app_id": "not-a-number",
  147. }, nil)
  148. require.Equal(t, http.StatusBadRequest, rec.Code)
  149. require.Contains(t, rec.Body.String(), "positive integer")
  150. }