oauth_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. //go:build unit
  2. package antigravity
  3. import (
  4. "crypto/sha256"
  5. "encoding/base64"
  6. "encoding/hex"
  7. "net/url"
  8. "os"
  9. "strings"
  10. "testing"
  11. "time"
  12. )
  13. // ---------------------------------------------------------------------------
  14. // getClientSecret
  15. // ---------------------------------------------------------------------------
  16. func TestGetClientSecret_环境变量设置(t *testing.T) {
  17. old := defaultClientSecret
  18. defaultClientSecret = ""
  19. t.Cleanup(func() { defaultClientSecret = old })
  20. t.Setenv(AntigravityOAuthClientSecretEnv, "my-secret-value")
  21. // 需要重新触发 init 逻辑:手动从环境变量读取
  22. defaultClientSecret = os.Getenv(AntigravityOAuthClientSecretEnv)
  23. secret, err := getClientSecret()
  24. if err != nil {
  25. t.Fatalf("获取 client_secret 失败: %v", err)
  26. }
  27. if secret != "my-secret-value" {
  28. t.Errorf("client_secret 不匹配: got %s, want my-secret-value", secret)
  29. }
  30. }
  31. func TestGetClientSecret_环境变量为空(t *testing.T) {
  32. old := defaultClientSecret
  33. defaultClientSecret = ""
  34. t.Cleanup(func() { defaultClientSecret = old })
  35. _, err := getClientSecret()
  36. if err == nil {
  37. t.Fatal("defaultClientSecret 为空时应返回错误")
  38. }
  39. if !strings.Contains(err.Error(), AntigravityOAuthClientSecretEnv) {
  40. t.Errorf("错误信息应包含环境变量名: got %s", err.Error())
  41. }
  42. }
  43. func TestGetClientSecret_环境变量未设置(t *testing.T) {
  44. old := defaultClientSecret
  45. defaultClientSecret = ""
  46. t.Cleanup(func() { defaultClientSecret = old })
  47. _, err := getClientSecret()
  48. if err == nil {
  49. t.Fatal("defaultClientSecret 为空时应返回错误")
  50. }
  51. }
  52. func TestGetClientSecret_环境变量含空格(t *testing.T) {
  53. old := defaultClientSecret
  54. defaultClientSecret = " "
  55. t.Cleanup(func() { defaultClientSecret = old })
  56. _, err := getClientSecret()
  57. if err == nil {
  58. t.Fatal("defaultClientSecret 仅含空格时应返回错误")
  59. }
  60. }
  61. func TestGetClientSecret_环境变量有前后空格(t *testing.T) {
  62. old := defaultClientSecret
  63. defaultClientSecret = " valid-secret "
  64. t.Cleanup(func() { defaultClientSecret = old })
  65. secret, err := getClientSecret()
  66. if err != nil {
  67. t.Fatalf("获取 client_secret 失败: %v", err)
  68. }
  69. if secret != "valid-secret" {
  70. t.Errorf("应去除前后空格: got %q, want %q", secret, "valid-secret")
  71. }
  72. }
  73. // ---------------------------------------------------------------------------
  74. // ForwardBaseURLs
  75. // ---------------------------------------------------------------------------
  76. func TestForwardBaseURLs_Daily优先(t *testing.T) {
  77. urls := ForwardBaseURLs()
  78. if len(urls) == 0 {
  79. t.Fatal("ForwardBaseURLs 返回空列表")
  80. }
  81. if antigravityDailyBaseURL != "https://daily-cloudcode-pa.googleapis.com" {
  82. t.Fatalf("daily URL 未与官方客户端对齐: got %s", antigravityDailyBaseURL)
  83. }
  84. // daily URL 应排在第一位
  85. if urls[0] != antigravityDailyBaseURL {
  86. t.Errorf("第一个 URL 应为 daily: got %s, want %s", urls[0], antigravityDailyBaseURL)
  87. }
  88. // 应包含所有 URL
  89. if len(urls) != len(BaseURLs) {
  90. t.Errorf("URL 数量不匹配: got %d, want %d", len(urls), len(BaseURLs))
  91. }
  92. // 验证 prod URL 也在列表中
  93. found := false
  94. for _, u := range urls {
  95. if u == antigravityProdBaseURL {
  96. found = true
  97. break
  98. }
  99. }
  100. if !found {
  101. t.Error("ForwardBaseURLs 中缺少 prod URL")
  102. }
  103. }
  104. func TestForwardBaseURLs_不修改原切片(t *testing.T) {
  105. originalFirst := BaseURLs[0]
  106. _ = ForwardBaseURLs()
  107. // 确保原始 BaseURLs 未被修改
  108. if BaseURLs[0] != originalFirst {
  109. t.Errorf("ForwardBaseURLs 不应修改原始 BaseURLs: got %s, want %s", BaseURLs[0], originalFirst)
  110. }
  111. }
  112. // ---------------------------------------------------------------------------
  113. // URLAvailability
  114. // ---------------------------------------------------------------------------
  115. func TestNewURLAvailability(t *testing.T) {
  116. ua := NewURLAvailability(5 * time.Minute)
  117. if ua == nil {
  118. t.Fatal("NewURLAvailability 返回 nil")
  119. }
  120. if ua.ttl != 5*time.Minute {
  121. t.Errorf("TTL 不匹配: got %v, want 5m", ua.ttl)
  122. }
  123. if ua.unavailable == nil {
  124. t.Error("unavailable map 不应为 nil")
  125. }
  126. }
  127. func TestURLAvailability_MarkUnavailable(t *testing.T) {
  128. ua := NewURLAvailability(5 * time.Minute)
  129. testURL := "https://example.com"
  130. ua.MarkUnavailable(testURL)
  131. if ua.IsAvailable(testURL) {
  132. t.Error("标记为不可用后 IsAvailable 应返回 false")
  133. }
  134. }
  135. func TestURLAvailability_MarkSuccess(t *testing.T) {
  136. ua := NewURLAvailability(5 * time.Minute)
  137. testURL := "https://example.com"
  138. // 先标记为不可用
  139. ua.MarkUnavailable(testURL)
  140. if ua.IsAvailable(testURL) {
  141. t.Error("标记为不可用后应不可用")
  142. }
  143. // 标记成功后应恢复可用
  144. ua.MarkSuccess(testURL)
  145. if !ua.IsAvailable(testURL) {
  146. t.Error("MarkSuccess 后应恢复可用")
  147. }
  148. // 验证 lastSuccess 被设置
  149. ua.mu.RLock()
  150. if ua.lastSuccess != testURL {
  151. t.Errorf("lastSuccess 不匹配: got %s, want %s", ua.lastSuccess, testURL)
  152. }
  153. ua.mu.RUnlock()
  154. }
  155. func TestURLAvailability_IsAvailable_TTL过期(t *testing.T) {
  156. // 使用极短的 TTL
  157. ua := NewURLAvailability(1 * time.Millisecond)
  158. testURL := "https://example.com"
  159. ua.MarkUnavailable(testURL)
  160. // 等待 TTL 过期
  161. time.Sleep(5 * time.Millisecond)
  162. if !ua.IsAvailable(testURL) {
  163. t.Error("TTL 过期后 URL 应恢复可用")
  164. }
  165. }
  166. func TestURLAvailability_IsAvailable_未标记的URL(t *testing.T) {
  167. ua := NewURLAvailability(5 * time.Minute)
  168. if !ua.IsAvailable("https://never-marked.com") {
  169. t.Error("未标记的 URL 应默认可用")
  170. }
  171. }
  172. func TestURLAvailability_GetAvailableURLs(t *testing.T) {
  173. ua := NewURLAvailability(10 * time.Minute)
  174. // 默认所有 URL 都可用
  175. urls := ua.GetAvailableURLs()
  176. if len(urls) != len(BaseURLs) {
  177. t.Errorf("可用 URL 数量不匹配: got %d, want %d", len(urls), len(BaseURLs))
  178. }
  179. }
  180. func TestURLAvailability_GetAvailableURLs_标记一个不可用(t *testing.T) {
  181. ua := NewURLAvailability(10 * time.Minute)
  182. if len(BaseURLs) < 2 {
  183. t.Skip("BaseURLs 少于 2 个,跳过此测试")
  184. }
  185. ua.MarkUnavailable(BaseURLs[0])
  186. urls := ua.GetAvailableURLs()
  187. // 标记的 URL 不应出现在可用列表中
  188. for _, u := range urls {
  189. if u == BaseURLs[0] {
  190. t.Errorf("被标记不可用的 URL 不应出现在可用列表中: %s", BaseURLs[0])
  191. }
  192. }
  193. }
  194. func TestURLAvailability_GetAvailableURLsWithBase(t *testing.T) {
  195. ua := NewURLAvailability(10 * time.Minute)
  196. customURLs := []string{"https://a.com", "https://b.com", "https://c.com"}
  197. urls := ua.GetAvailableURLsWithBase(customURLs)
  198. if len(urls) != 3 {
  199. t.Errorf("可用 URL 数量不匹配: got %d, want 3", len(urls))
  200. }
  201. }
  202. func TestURLAvailability_GetAvailableURLsWithBase_LastSuccess优先(t *testing.T) {
  203. ua := NewURLAvailability(10 * time.Minute)
  204. customURLs := []string{"https://a.com", "https://b.com", "https://c.com"}
  205. ua.MarkSuccess("https://c.com")
  206. urls := ua.GetAvailableURLsWithBase(customURLs)
  207. if len(urls) != 3 {
  208. t.Fatalf("可用 URL 数量不匹配: got %d, want 3", len(urls))
  209. }
  210. // c.com 应排在第一位
  211. if urls[0] != "https://c.com" {
  212. t.Errorf("lastSuccess 应排在第一位: got %s, want https://c.com", urls[0])
  213. }
  214. // 其余按原始顺序
  215. if urls[1] != "https://a.com" {
  216. t.Errorf("第二个应为 a.com: got %s", urls[1])
  217. }
  218. if urls[2] != "https://b.com" {
  219. t.Errorf("第三个应为 b.com: got %s", urls[2])
  220. }
  221. }
  222. func TestURLAvailability_GetAvailableURLsWithBase_LastSuccess不可用(t *testing.T) {
  223. ua := NewURLAvailability(10 * time.Minute)
  224. customURLs := []string{"https://a.com", "https://b.com"}
  225. ua.MarkSuccess("https://b.com")
  226. ua.MarkUnavailable("https://b.com")
  227. urls := ua.GetAvailableURLsWithBase(customURLs)
  228. // b.com 被标记不可用,不应出现
  229. if len(urls) != 1 {
  230. t.Fatalf("可用 URL 数量不匹配: got %d, want 1", len(urls))
  231. }
  232. if urls[0] != "https://a.com" {
  233. t.Errorf("仅 a.com 应可用: got %s", urls[0])
  234. }
  235. }
  236. func TestURLAvailability_GetAvailableURLsWithBase_LastSuccess不在列表中(t *testing.T) {
  237. ua := NewURLAvailability(10 * time.Minute)
  238. customURLs := []string{"https://a.com", "https://b.com"}
  239. ua.MarkSuccess("https://not-in-list.com")
  240. urls := ua.GetAvailableURLsWithBase(customURLs)
  241. // lastSuccess 不在自定义列表中,不应被添加
  242. if len(urls) != 2 {
  243. t.Fatalf("可用 URL 数量不匹配: got %d, want 2", len(urls))
  244. }
  245. }
  246. // ---------------------------------------------------------------------------
  247. // SessionStore
  248. // ---------------------------------------------------------------------------
  249. func TestNewSessionStore(t *testing.T) {
  250. store := NewSessionStore()
  251. defer store.Stop()
  252. if store == nil {
  253. t.Fatal("NewSessionStore 返回 nil")
  254. }
  255. if store.sessions == nil {
  256. t.Error("sessions map 不应为 nil")
  257. }
  258. }
  259. func TestSessionStore_SetAndGet(t *testing.T) {
  260. store := NewSessionStore()
  261. defer store.Stop()
  262. session := &OAuthSession{
  263. State: "test-state",
  264. CodeVerifier: "test-verifier",
  265. ProxyURL: "http://proxy.example.com",
  266. CreatedAt: time.Now(),
  267. }
  268. store.Set("session-1", session)
  269. got, ok := store.Get("session-1")
  270. if !ok {
  271. t.Fatal("Get 应返回 true")
  272. }
  273. if got.State != "test-state" {
  274. t.Errorf("State 不匹配: got %s", got.State)
  275. }
  276. if got.CodeVerifier != "test-verifier" {
  277. t.Errorf("CodeVerifier 不匹配: got %s", got.CodeVerifier)
  278. }
  279. if got.ProxyURL != "http://proxy.example.com" {
  280. t.Errorf("ProxyURL 不匹配: got %s", got.ProxyURL)
  281. }
  282. }
  283. func TestSessionStore_Get_不存在(t *testing.T) {
  284. store := NewSessionStore()
  285. defer store.Stop()
  286. _, ok := store.Get("nonexistent")
  287. if ok {
  288. t.Error("不存在的 session 应返回 false")
  289. }
  290. }
  291. func TestSessionStore_Get_过期(t *testing.T) {
  292. store := NewSessionStore()
  293. defer store.Stop()
  294. session := &OAuthSession{
  295. State: "expired-state",
  296. CreatedAt: time.Now().Add(-SessionTTL - time.Minute), // 已过期
  297. }
  298. store.Set("expired-session", session)
  299. _, ok := store.Get("expired-session")
  300. if ok {
  301. t.Error("过期的 session 应返回 false")
  302. }
  303. }
  304. func TestSessionStore_Delete(t *testing.T) {
  305. store := NewSessionStore()
  306. defer store.Stop()
  307. session := &OAuthSession{
  308. State: "to-delete",
  309. CreatedAt: time.Now(),
  310. }
  311. store.Set("del-session", session)
  312. store.Delete("del-session")
  313. _, ok := store.Get("del-session")
  314. if ok {
  315. t.Error("删除后 Get 应返回 false")
  316. }
  317. }
  318. func TestSessionStore_Delete_不存在(t *testing.T) {
  319. store := NewSessionStore()
  320. defer store.Stop()
  321. // 删除不存在的 session 不应 panic
  322. store.Delete("nonexistent")
  323. }
  324. func TestSessionStore_Stop(t *testing.T) {
  325. store := NewSessionStore()
  326. store.Stop()
  327. // 多次 Stop 不应 panic
  328. store.Stop()
  329. }
  330. func TestSessionStore_多个Session(t *testing.T) {
  331. store := NewSessionStore()
  332. defer store.Stop()
  333. for i := 0; i < 10; i++ {
  334. session := &OAuthSession{
  335. State: "state-" + string(rune('0'+i)),
  336. CreatedAt: time.Now(),
  337. }
  338. store.Set("session-"+string(rune('0'+i)), session)
  339. }
  340. // 验证都能取到
  341. for i := 0; i < 10; i++ {
  342. _, ok := store.Get("session-" + string(rune('0'+i)))
  343. if !ok {
  344. t.Errorf("session-%d 应存在", i)
  345. }
  346. }
  347. }
  348. // ---------------------------------------------------------------------------
  349. // GenerateRandomBytes
  350. // ---------------------------------------------------------------------------
  351. func TestGenerateRandomBytes_长度正确(t *testing.T) {
  352. sizes := []int{0, 1, 16, 32, 64, 128}
  353. for _, size := range sizes {
  354. b, err := GenerateRandomBytes(size)
  355. if err != nil {
  356. t.Fatalf("GenerateRandomBytes(%d) 失败: %v", size, err)
  357. }
  358. if len(b) != size {
  359. t.Errorf("长度不匹配: got %d, want %d", len(b), size)
  360. }
  361. }
  362. }
  363. func TestGenerateRandomBytes_不同调用产生不同结果(t *testing.T) {
  364. b1, err := GenerateRandomBytes(32)
  365. if err != nil {
  366. t.Fatalf("第一次调用失败: %v", err)
  367. }
  368. b2, err := GenerateRandomBytes(32)
  369. if err != nil {
  370. t.Fatalf("第二次调用失败: %v", err)
  371. }
  372. // 两次生成的随机字节应该不同(概率上几乎不可能相同)
  373. if string(b1) == string(b2) {
  374. t.Error("两次生成的随机字节相同,概率极低,可能有问题")
  375. }
  376. }
  377. // ---------------------------------------------------------------------------
  378. // GenerateState
  379. // ---------------------------------------------------------------------------
  380. func TestGenerateState_返回值格式(t *testing.T) {
  381. state, err := GenerateState()
  382. if err != nil {
  383. t.Fatalf("GenerateState 失败: %v", err)
  384. }
  385. if state == "" {
  386. t.Error("GenerateState 返回空字符串")
  387. }
  388. // base64url 编码不应包含 +, /, =
  389. if strings.ContainsAny(state, "+/=") {
  390. t.Errorf("GenerateState 返回值包含非 base64url 字符: %s", state)
  391. }
  392. // 32 字节的 base64url 编码长度应为 43(去掉了尾部 = 填充)
  393. if len(state) != 43 {
  394. t.Errorf("GenerateState 返回值长度不匹配: got %d, want 43", len(state))
  395. }
  396. }
  397. func TestGenerateState_唯一性(t *testing.T) {
  398. s1, _ := GenerateState()
  399. s2, _ := GenerateState()
  400. if s1 == s2 {
  401. t.Error("两次 GenerateState 结果相同")
  402. }
  403. }
  404. // ---------------------------------------------------------------------------
  405. // GenerateSessionID
  406. // ---------------------------------------------------------------------------
  407. func TestGenerateSessionID_返回值格式(t *testing.T) {
  408. id, err := GenerateSessionID()
  409. if err != nil {
  410. t.Fatalf("GenerateSessionID 失败: %v", err)
  411. }
  412. if id == "" {
  413. t.Error("GenerateSessionID 返回空字符串")
  414. }
  415. // 16 字节的 hex 编码长度应为 32
  416. if len(id) != 32 {
  417. t.Errorf("GenerateSessionID 返回值长度不匹配: got %d, want 32", len(id))
  418. }
  419. // 验证是合法的 hex 字符串
  420. if _, err := hex.DecodeString(id); err != nil {
  421. t.Errorf("GenerateSessionID 返回值不是合法的 hex 字符串: %s, err: %v", id, err)
  422. }
  423. }
  424. func TestGenerateSessionID_唯一性(t *testing.T) {
  425. id1, _ := GenerateSessionID()
  426. id2, _ := GenerateSessionID()
  427. if id1 == id2 {
  428. t.Error("两次 GenerateSessionID 结果相同")
  429. }
  430. }
  431. // ---------------------------------------------------------------------------
  432. // GenerateCodeVerifier
  433. // ---------------------------------------------------------------------------
  434. func TestGenerateCodeVerifier_返回值格式(t *testing.T) {
  435. verifier, err := GenerateCodeVerifier()
  436. if err != nil {
  437. t.Fatalf("GenerateCodeVerifier 失败: %v", err)
  438. }
  439. if verifier == "" {
  440. t.Error("GenerateCodeVerifier 返回空字符串")
  441. }
  442. // base64url 编码不应包含 +, /, =
  443. if strings.ContainsAny(verifier, "+/=") {
  444. t.Errorf("GenerateCodeVerifier 返回值包含非 base64url 字符: %s", verifier)
  445. }
  446. // 32 字节的 base64url 编码长度应为 43
  447. if len(verifier) != 43 {
  448. t.Errorf("GenerateCodeVerifier 返回值长度不匹配: got %d, want 43", len(verifier))
  449. }
  450. }
  451. func TestGenerateCodeVerifier_唯一性(t *testing.T) {
  452. v1, _ := GenerateCodeVerifier()
  453. v2, _ := GenerateCodeVerifier()
  454. if v1 == v2 {
  455. t.Error("两次 GenerateCodeVerifier 结果相同")
  456. }
  457. }
  458. // ---------------------------------------------------------------------------
  459. // GenerateCodeChallenge
  460. // ---------------------------------------------------------------------------
  461. func TestGenerateCodeChallenge_SHA256_Base64URL(t *testing.T) {
  462. verifier := "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
  463. challenge := GenerateCodeChallenge(verifier)
  464. // 手动计算预期值
  465. hash := sha256.Sum256([]byte(verifier))
  466. expected := strings.TrimRight(base64.URLEncoding.EncodeToString(hash[:]), "=")
  467. if challenge != expected {
  468. t.Errorf("CodeChallenge 不匹配: got %s, want %s", challenge, expected)
  469. }
  470. }
  471. func TestGenerateCodeChallenge_不含填充字符(t *testing.T) {
  472. challenge := GenerateCodeChallenge("test-verifier")
  473. if strings.Contains(challenge, "=") {
  474. t.Errorf("CodeChallenge 不应包含 = 填充字符: %s", challenge)
  475. }
  476. }
  477. func TestGenerateCodeChallenge_不含非URL安全字符(t *testing.T) {
  478. challenge := GenerateCodeChallenge("another-verifier")
  479. if strings.ContainsAny(challenge, "+/") {
  480. t.Errorf("CodeChallenge 不应包含 + 或 / 字符: %s", challenge)
  481. }
  482. }
  483. func TestGenerateCodeChallenge_相同输入相同输出(t *testing.T) {
  484. c1 := GenerateCodeChallenge("same-verifier")
  485. c2 := GenerateCodeChallenge("same-verifier")
  486. if c1 != c2 {
  487. t.Errorf("相同输入应产生相同输出: got %s and %s", c1, c2)
  488. }
  489. }
  490. func TestGenerateCodeChallenge_不同输入不同输出(t *testing.T) {
  491. c1 := GenerateCodeChallenge("verifier-1")
  492. c2 := GenerateCodeChallenge("verifier-2")
  493. if c1 == c2 {
  494. t.Error("不同输入应产生不同输出")
  495. }
  496. }
  497. // ---------------------------------------------------------------------------
  498. // BuildAuthorizationURL
  499. // ---------------------------------------------------------------------------
  500. func TestBuildAuthorizationURL_参数验证(t *testing.T) {
  501. state := "test-state-123"
  502. codeChallenge := "test-challenge-abc"
  503. authURL := BuildAuthorizationURL(state, codeChallenge)
  504. // 验证以 AuthorizeURL 开头
  505. if !strings.HasPrefix(authURL, AuthorizeURL+"?") {
  506. t.Errorf("URL 应以 %s? 开头: got %s", AuthorizeURL, authURL)
  507. }
  508. // 解析 URL 并验证参数
  509. parsed, err := url.Parse(authURL)
  510. if err != nil {
  511. t.Fatalf("解析 URL 失败: %v", err)
  512. }
  513. params := parsed.Query()
  514. expectedParams := map[string]string{
  515. "client_id": ClientID,
  516. "redirect_uri": RedirectURI,
  517. "response_type": "code",
  518. "scope": Scopes,
  519. "state": state,
  520. "code_challenge": codeChallenge,
  521. "code_challenge_method": "S256",
  522. "access_type": "offline",
  523. "prompt": "consent",
  524. "include_granted_scopes": "true",
  525. }
  526. for key, want := range expectedParams {
  527. got := params.Get(key)
  528. if got != want {
  529. t.Errorf("参数 %s 不匹配: got %q, want %q", key, got, want)
  530. }
  531. }
  532. }
  533. func TestBuildAuthorizationURL_参数数量(t *testing.T) {
  534. authURL := BuildAuthorizationURL("s", "c")
  535. parsed, err := url.Parse(authURL)
  536. if err != nil {
  537. t.Fatalf("解析 URL 失败: %v", err)
  538. }
  539. params := parsed.Query()
  540. // 应包含 10 个参数
  541. expectedCount := 10
  542. if len(params) != expectedCount {
  543. t.Errorf("参数数量不匹配: got %d, want %d", len(params), expectedCount)
  544. }
  545. }
  546. func TestBuildAuthorizationURL_特殊字符编码(t *testing.T) {
  547. state := "state+with/special=chars"
  548. codeChallenge := "challenge+value"
  549. authURL := BuildAuthorizationURL(state, codeChallenge)
  550. parsed, err := url.Parse(authURL)
  551. if err != nil {
  552. t.Fatalf("解析 URL 失败: %v", err)
  553. }
  554. // 解析后应正确还原特殊字符
  555. if got := parsed.Query().Get("state"); got != state {
  556. t.Errorf("state 参数编码/解码不匹配: got %q, want %q", got, state)
  557. }
  558. }
  559. // ---------------------------------------------------------------------------
  560. // 常量值验证
  561. // ---------------------------------------------------------------------------
  562. func TestConstants_值正确(t *testing.T) {
  563. if AuthorizeURL != "https://accounts.google.com/o/oauth2/v2/auth" {
  564. t.Errorf("AuthorizeURL 不匹配: got %s", AuthorizeURL)
  565. }
  566. if TokenURL != "https://oauth2.googleapis.com/token" {
  567. t.Errorf("TokenURL 不匹配: got %s", TokenURL)
  568. }
  569. if UserInfoURL != "https://www.googleapis.com/oauth2/v2/userinfo" {
  570. t.Errorf("UserInfoURL 不匹配: got %s", UserInfoURL)
  571. }
  572. if ClientID != "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com" {
  573. t.Errorf("ClientID 不匹配: got %s", ClientID)
  574. }
  575. secret, err := getClientSecret()
  576. if err != nil {
  577. t.Fatalf("getClientSecret 应返回默认值,但报错: %v", err)
  578. }
  579. if secret != "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" {
  580. t.Errorf("默认 client_secret 不匹配: got %s", secret)
  581. }
  582. if RedirectURI != "http://localhost:8085/callback" {
  583. t.Errorf("RedirectURI 不匹配: got %s", RedirectURI)
  584. }
  585. if GetUserAgent() != "antigravity/2.9.1 windows/amd64" {
  586. t.Errorf("UserAgent 不匹配: got %s", GetUserAgent())
  587. }
  588. if SessionTTL != 30*time.Minute {
  589. t.Errorf("SessionTTL 不匹配: got %v", SessionTTL)
  590. }
  591. if URLAvailabilityTTL != 5*time.Minute {
  592. t.Errorf("URLAvailabilityTTL 不匹配: got %v", URLAvailabilityTTL)
  593. }
  594. }
  595. func TestScopes_包含必要范围(t *testing.T) {
  596. expectedScopes := []string{
  597. "https://www.googleapis.com/auth/cloud-platform",
  598. "https://www.googleapis.com/auth/userinfo.email",
  599. "https://www.googleapis.com/auth/userinfo.profile",
  600. "https://www.googleapis.com/auth/cclog",
  601. "https://www.googleapis.com/auth/experimentsandconfigs",
  602. }
  603. for _, scope := range expectedScopes {
  604. if !strings.Contains(Scopes, scope) {
  605. t.Errorf("Scopes 缺少 %s", scope)
  606. }
  607. }
  608. }