auth_dingtalk_client_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package handler
  2. import (
  3. "context"
  4. "net/http"
  5. "net/http/httptest"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestDingTalkClient_ExchangeCodeForUserToken_Success(t *testing.T) {
  11. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  12. require.Equal(t, "POST", r.Method)
  13. require.Equal(t, "/v1.0/oauth2/userAccessToken", r.URL.Path)
  14. w.Header().Set("Content-Type", "application/json")
  15. _, _ = w.Write([]byte(`{"accessToken":"USER_TOKEN_X","expireIn":7200,"refreshToken":"R","corpId":"dingABC"}`))
  16. }))
  17. defer server.Close()
  18. cli := &DingTalkClient{
  19. cfg: dingTalkClientConfig{
  20. ClientID: "k", ClientSecret: "s",
  21. TokenURL: server.URL + "/v1.0/oauth2/userAccessToken",
  22. },
  23. httpClient: server.Client(),
  24. }
  25. resp, err := cli.ExchangeCodeForUserToken(context.Background(), "AUTH_CODE")
  26. require.NoError(t, err)
  27. require.Equal(t, "USER_TOKEN_X", resp.AccessToken)
  28. require.Equal(t, "dingABC", resp.CorpID)
  29. }
  30. func TestDingTalkClient_GetUnionIdByUserToken_Success(t *testing.T) {
  31. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  32. require.Equal(t, "USER_TOKEN_X", r.Header.Get("x-acs-dingtalk-access-token"))
  33. w.Header().Set("Content-Type", "application/json")
  34. _, _ = w.Write([]byte(`{"nick":"张三","unionId":"UID_AAA","openId":"OPEN","avatarUrl":"http://x"}`))
  35. }))
  36. defer server.Close()
  37. cli := &DingTalkClient{
  38. cfg: dingTalkClientConfig{UserInfoURL: server.URL + "/v1.0/contact/users/me"},
  39. httpClient: server.Client(),
  40. }
  41. unionID, nick, err := cli.GetUnionIdByUserToken(context.Background(), "USER_TOKEN_X")
  42. require.NoError(t, err)
  43. require.Equal(t, "UID_AAA", unionID)
  44. require.Equal(t, "张三", nick)
  45. }
  46. func TestDingTalkClient_GetAppToken_Cached(t *testing.T) {
  47. callCount := 0
  48. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  49. callCount++
  50. _, _ = w.Write([]byte(`{"accessToken":"APP_TKN","expireIn":7200}`))
  51. }))
  52. defer server.Close()
  53. cli := &DingTalkClient{
  54. cfg: dingTalkClientConfig{ClientID: "k", ClientSecret: "s", TokenURL: server.URL + "/gettoken"},
  55. httpClient: server.Client(),
  56. }
  57. t1, err := cli.GetAppToken(context.Background())
  58. require.NoError(t, err)
  59. t2, err := cli.GetAppToken(context.Background())
  60. require.NoError(t, err)
  61. require.Equal(t, t1, t2)
  62. require.Equal(t, 1, callCount, "second call should hit cache")
  63. }
  64. func TestDingTalkClient_GetUserIdByUnionId_60011(t *testing.T) {
  65. appTokenServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  66. _, _ = w.Write([]byte(`{"accessToken":"APP_TKN","expireIn":7200}`))
  67. }))
  68. defer appTokenServer.Close()
  69. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  70. w.Header().Set("Content-Type", "application/json")
  71. _, _ = w.Write([]byte(`{"errcode":60011,"errmsg":"not in directory"}`))
  72. }))
  73. defer server.Close()
  74. cli := &DingTalkClient{
  75. cfg: dingTalkClientConfig{TokenURL: appTokenServer.URL + "/gettoken"},
  76. httpClient: server.Client(),
  77. }
  78. cli.appToken = "APP_TKN"
  79. cli.appTokenExp = time.Now().Add(time.Hour)
  80. cli.cfg.UserInfoURL = server.URL + "/v1.0/contact/users/byUnionId"
  81. _, err := cli.GetUserIdByUnionId(context.Background(), "UID_AAA")
  82. require.Error(t, err)
  83. apiErr, ok := err.(*DingTalkAPIError)
  84. require.True(t, ok)
  85. require.Equal(t, "60011", apiErr.Code)
  86. }
  87. // TestDingTalkClient_GetDeptInfo_Success 验证 GetDeptInfo 正常情况返回部门信息。
  88. func TestDingTalkClient_GetDeptInfo_Success(t *testing.T) {
  89. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  90. w.Header().Set("Content-Type", "application/json")
  91. _, _ = w.Write([]byte(`{"errcode":0,"errmsg":"ok","result":{"dept_id":42,"name":"AI数据","parent_id":1}}`))
  92. }))
  93. defer server.Close()
  94. cli := &DingTalkClient{
  95. cfg: dingTalkClientConfig{
  96. UserInfoURL: server.URL + "/stub", // 不含 /contact/users/me,走 test stub 路径
  97. },
  98. httpClient: server.Client(),
  99. }
  100. cli.appToken = "APP_TKN"
  101. cli.appTokenExp = time.Now().Add(time.Hour)
  102. info, err := cli.GetDeptInfo(context.Background(), 42)
  103. require.NoError(t, err)
  104. require.Equal(t, int64(42), info.DeptID)
  105. require.Equal(t, "AI数据", info.Name)
  106. require.Equal(t, int64(1), info.ParentID)
  107. }
  108. // TestDingTalkClient_GetDeptInfo_ErrCode60003 验证 errcode=60003(部门不存在)时返回错误。
  109. func TestDingTalkClient_GetDeptInfo_ErrCode60003(t *testing.T) {
  110. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  111. w.Header().Set("Content-Type", "application/json")
  112. _, _ = w.Write([]byte(`{"errcode":60003,"errmsg":"dept not found"}`))
  113. }))
  114. defer server.Close()
  115. cli := &DingTalkClient{
  116. cfg: dingTalkClientConfig{UserInfoURL: server.URL + "/stub"},
  117. httpClient: server.Client(),
  118. }
  119. cli.appToken = "APP_TKN"
  120. cli.appTokenExp = time.Now().Add(time.Hour)
  121. _, err := cli.GetDeptInfo(context.Background(), 999)
  122. require.Error(t, err)
  123. apiErr, ok := err.(*DingTalkAPIError)
  124. require.True(t, ok)
  125. require.Equal(t, "60003", apiErr.Code)
  126. }