/github/resource_github_repository_collaborator_test.go

https://github.com/terraform-providers/terraform-provider-github · Go · 397 lines · 325 code · 60 blank · 12 comment · 59 complexity · 3677dca0c4db2c0f58fba7caa10ffc9a MD5 · raw file

  1. package github
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "regexp"
  7. "strings"
  8. "testing"
  9. "github.com/google/go-github/v32/github"
  10. "github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
  11. "github.com/hashicorp/terraform-plugin-sdk/helper/resource"
  12. "github.com/hashicorp/terraform-plugin-sdk/terraform"
  13. )
  14. // TestAccGithubRepositoryCollaborator_basic adds a collaborator
  15. // with permissions supported by an organization-owned repository
  16. // i.e. admin, triage, push, or pull
  17. func TestAccGithubRepositoryCollaborator_basic(t *testing.T) {
  18. if testCollaborator == "" {
  19. t.Skip("Skipping because `GITHUB_TEST_COLLABORATOR` is not set")
  20. }
  21. if err := testAccCheckOrganization(); err != nil {
  22. t.Skipf("Skipping because %s", err.Error())
  23. }
  24. rn := "github_repository_collaborator.test_repo_collaborator"
  25. repoName := fmt.Sprintf("tf-acc-test-collab-%s", acctest.RandString(5))
  26. permissionAdmin := "admin"
  27. permissionTriage := "triage"
  28. permissionPush := "push"
  29. permissionPull := "pull"
  30. resource.ParallelTest(t, resource.TestCase{
  31. PreCheck: func() { testAccPreCheck(t) },
  32. Providers: testAccProviders,
  33. CheckDestroy: testAccCheckGithubRepositoryCollaboratorDestroy,
  34. Steps: []resource.TestStep{
  35. {
  36. Config: testAccGithubRepositoryCollaboratorConfig(repoName, testCollaborator, permissionPull),
  37. Check: resource.ComposeTestCheckFunc(
  38. testAccCheckGithubRepositoryCollaboratorExists(rn),
  39. testAccCheckGithubRepositoryCollaboratorPermission(rn, permissionPull),
  40. resource.TestCheckResourceAttr(rn, "permission", permissionPull),
  41. resource.TestMatchResourceAttr(rn, "invitation_id", regexp.MustCompile(`^[0-9]+$`)),
  42. ),
  43. },
  44. {
  45. Config: testAccGithubRepositoryCollaboratorConfig(repoName, testCollaborator, permissionPush),
  46. Check: resource.ComposeTestCheckFunc(
  47. testAccCheckGithubRepositoryCollaboratorExists(rn),
  48. testAccCheckGithubRepositoryCollaboratorPermission(rn, permissionPush),
  49. resource.TestCheckResourceAttr(rn, "permission", permissionPush),
  50. resource.TestMatchResourceAttr(rn, "invitation_id", regexp.MustCompile(`^[0-9]+$`)),
  51. ),
  52. },
  53. {
  54. Config: testAccGithubRepositoryCollaboratorConfig(repoName, testCollaborator, permissionAdmin),
  55. Check: resource.ComposeTestCheckFunc(
  56. testAccCheckGithubRepositoryCollaboratorExists(rn),
  57. testAccCheckGithubRepositoryCollaboratorPermission(rn, permissionAdmin),
  58. resource.TestCheckResourceAttr(rn, "permission", permissionAdmin),
  59. resource.TestMatchResourceAttr(rn, "invitation_id", regexp.MustCompile(`^[0-9]+$`)),
  60. ),
  61. },
  62. {
  63. Config: testAccGithubRepositoryCollaboratorConfig(repoName, testCollaborator, permissionTriage),
  64. Check: resource.ComposeTestCheckFunc(
  65. testAccCheckGithubRepositoryCollaboratorExists(rn),
  66. testAccCheckGithubRepositoryCollaboratorPermission(rn, permissionTriage),
  67. resource.TestCheckResourceAttr(rn, "permission", permissionTriage),
  68. resource.TestMatchResourceAttr(rn, "invitation_id", regexp.MustCompile(`^[0-9]+$`)),
  69. ),
  70. },
  71. {
  72. ResourceName: rn,
  73. ImportState: true,
  74. ImportStateVerify: true,
  75. },
  76. },
  77. })
  78. }
  79. // TestAccGithubRepositoryCollaborator_basic_personal
  80. // adds a collaborator with permissions supported by
  81. // a personal repository i.e. push
  82. func TestAccGithubRepositoryCollaborator_basic_personal(t *testing.T) {
  83. if testCollaborator == "" {
  84. t.Skip("Skipping because `GITHUB_TEST_COLLABORATOR` is not set")
  85. }
  86. rn := "github_repository_collaborator.test_repo_collaborator"
  87. repoName := fmt.Sprintf("tf-acc-test-collab-%s", acctest.RandString(5))
  88. permissionPush := "push"
  89. resource.ParallelTest(t, resource.TestCase{
  90. PreCheck: func() { testAccPreCheck(t) },
  91. Providers: testAccProviders,
  92. CheckDestroy: testAccCheckGithubRepositoryCollaboratorDestroy,
  93. Steps: []resource.TestStep{
  94. {
  95. Config: testAccGithubRepositoryCollaboratorConfig(repoName, testCollaborator, permissionPush),
  96. Check: resource.ComposeTestCheckFunc(
  97. testAccCheckGithubRepositoryCollaboratorExists(rn),
  98. testAccCheckGithubRepositoryCollaboratorPermission(rn, permissionPush),
  99. resource.TestCheckResourceAttr(rn, "permission", permissionPush),
  100. resource.TestMatchResourceAttr(rn, "invitation_id", regexp.MustCompile(`^[0-9]+$`)),
  101. ),
  102. },
  103. {
  104. ResourceName: rn,
  105. ImportState: true,
  106. ImportStateVerify: true,
  107. },
  108. },
  109. })
  110. }
  111. // TestAccGithubRepositoryCollaborator_caseInsensitive
  112. // adds a collaborator with maintain permissions;
  113. // only supported by an organization-owned repository
  114. func TestAccGithubRepositoryCollaborator_caseInsensitive(t *testing.T) {
  115. if testCollaborator == "" {
  116. t.Skip("Skipping because `GITHUB_TEST_COLLABORATOR` is not set")
  117. }
  118. if err := testAccCheckOrganization(); err != nil {
  119. t.Skipf("Skipping because %s.", err.Error())
  120. }
  121. rn := "github_repository_collaborator.test_repo_collaborator"
  122. repoName := fmt.Sprintf("tf-acc-test-collab-%s", acctest.RandString(5))
  123. var origInvitation github.RepositoryInvitation
  124. var otherInvitation github.RepositoryInvitation
  125. expectedPermission := "maintain"
  126. otherCase := flipUsernameCase(testCollaborator)
  127. if testCollaborator == otherCase {
  128. t.Skip("Skipping because `GITHUB_TEST_COLLABORATOR` has no letters to flip case")
  129. }
  130. resource.ParallelTest(t, resource.TestCase{
  131. PreCheck: func() { testAccPreCheck(t) },
  132. Providers: testAccProviders,
  133. CheckDestroy: testAccCheckGithubRepositoryCollaboratorDestroy,
  134. Steps: []resource.TestStep{
  135. {
  136. Config: testAccGithubRepositoryCollaboratorConfig(repoName, testCollaborator, expectedPermission),
  137. Check: resource.ComposeTestCheckFunc(
  138. testAccCheckGithubRepositoryCollaboratorInvited(repoName, testCollaborator, &origInvitation),
  139. ),
  140. },
  141. {
  142. Config: testAccGithubRepositoryCollaboratorConfig(repoName, otherCase, expectedPermission),
  143. Check: resource.ComposeTestCheckFunc(
  144. testAccCheckGithubRepositoryCollaboratorInvited(repoName, otherCase, &otherInvitation),
  145. resource.TestCheckResourceAttr(rn, "username", testCollaborator),
  146. testAccGithubRepositoryCollaboratorTheSame(&origInvitation, &otherInvitation),
  147. ),
  148. },
  149. {
  150. ResourceName: rn,
  151. ImportState: true,
  152. ImportStateVerify: true,
  153. },
  154. },
  155. })
  156. }
  157. // TestAccGithubRepositoryCollaborator_caseInsensitive_personal
  158. // adds a collaborator with push permissions; supported by both
  159. // a personal and an organization-owned repository
  160. func TestAccGithubRepositoryCollaborator_caseInsensitive_personal(t *testing.T) {
  161. if testCollaborator == "" {
  162. t.Skip("Skipping because `GITHUB_TEST_COLLABORATOR` is not set")
  163. }
  164. rn := "github_repository_collaborator.test_repo_collaborator"
  165. repoName := fmt.Sprintf("tf-acc-test-collab-%s", acctest.RandString(5))
  166. var origInvitation github.RepositoryInvitation
  167. var otherInvitation github.RepositoryInvitation
  168. expectedPermission := "push"
  169. otherCase := flipUsernameCase(testCollaborator)
  170. if testCollaborator == otherCase {
  171. t.Skip("Skipping because `GITHUB_TEST_COLLABORATOR` has no letters to flip case")
  172. }
  173. resource.ParallelTest(t, resource.TestCase{
  174. PreCheck: func() { testAccPreCheck(t) },
  175. Providers: testAccProviders,
  176. CheckDestroy: testAccCheckGithubRepositoryCollaboratorDestroy,
  177. Steps: []resource.TestStep{
  178. {
  179. Config: testAccGithubRepositoryCollaboratorConfig(repoName, testCollaborator, expectedPermission),
  180. Check: resource.ComposeTestCheckFunc(
  181. testAccCheckGithubRepositoryCollaboratorInvited(repoName, testCollaborator, &origInvitation),
  182. ),
  183. },
  184. {
  185. Config: testAccGithubRepositoryCollaboratorConfig(repoName, otherCase, expectedPermission),
  186. Check: resource.ComposeTestCheckFunc(
  187. testAccCheckGithubRepositoryCollaboratorInvited(repoName, otherCase, &otherInvitation),
  188. resource.TestCheckResourceAttr(rn, "username", testCollaborator),
  189. testAccGithubRepositoryCollaboratorTheSame(&origInvitation, &otherInvitation),
  190. ),
  191. },
  192. {
  193. ResourceName: rn,
  194. ImportState: true,
  195. ImportStateVerify: true,
  196. },
  197. },
  198. })
  199. }
  200. func testAccCheckGithubRepositoryCollaboratorDestroy(s *terraform.State) error {
  201. conn := testAccProvider.Meta().(*Owner).v3client
  202. for _, rs := range s.RootModule().Resources {
  203. if rs.Type != "github_repository_collaborator" {
  204. continue
  205. }
  206. o := testAccProvider.Meta().(*Owner).name
  207. r, u, err := parseTwoPartID(rs.Primary.ID, "repository", "username")
  208. if err != nil {
  209. return err
  210. }
  211. isCollaborator, _, err := conn.Repositories.IsCollaborator(context.TODO(), o, r, u)
  212. if err != nil {
  213. return err
  214. }
  215. if isCollaborator {
  216. return fmt.Errorf("Repository collaborator still exists")
  217. }
  218. return nil
  219. }
  220. return nil
  221. }
  222. func testAccCheckGithubRepositoryCollaboratorExists(n string) resource.TestCheckFunc {
  223. return func(s *terraform.State) error {
  224. rs, ok := s.RootModule().Resources[n]
  225. if !ok {
  226. return fmt.Errorf("Not Found: %s", n)
  227. }
  228. if rs.Primary.ID == "" {
  229. return fmt.Errorf("No membership ID is set")
  230. }
  231. conn := testAccProvider.Meta().(*Owner).v3client
  232. owner := testAccProvider.Meta().(*Owner).name
  233. repoName, username, err := parseTwoPartID(rs.Primary.ID, "repository", "username")
  234. if err != nil {
  235. return err
  236. }
  237. invitations, _, err := conn.Repositories.ListInvitations(context.TODO(),
  238. owner, repoName, nil)
  239. if err != nil {
  240. return err
  241. }
  242. hasInvitation := false
  243. for _, i := range invitations {
  244. if i.GetInvitee().GetLogin() == username {
  245. hasInvitation = true
  246. break
  247. }
  248. }
  249. if !hasInvitation {
  250. return fmt.Errorf("Repository collaboration invitation does not exist")
  251. }
  252. return nil
  253. }
  254. }
  255. func testAccCheckGithubRepositoryCollaboratorPermission(n, permission string) resource.TestCheckFunc {
  256. return func(s *terraform.State) error {
  257. rs, ok := s.RootModule().Resources[n]
  258. if !ok {
  259. return fmt.Errorf("Not Found: %s", n)
  260. }
  261. if rs.Primary.ID == "" {
  262. return fmt.Errorf("No membership ID is set")
  263. }
  264. conn := testAccProvider.Meta().(*Owner).v3client
  265. owner := testAccProvider.Meta().(*Owner).name
  266. repoName, username, err := parseTwoPartID(rs.Primary.ID, "repository", "username")
  267. if err != nil {
  268. return err
  269. }
  270. invitations, _, err := conn.Repositories.ListInvitations(context.TODO(),
  271. owner, repoName, nil)
  272. if err != nil {
  273. return err
  274. }
  275. for _, i := range invitations {
  276. if i.GetInvitee().GetLogin() == username {
  277. permName, err := getInvitationPermission(i)
  278. if err != nil {
  279. return err
  280. }
  281. if permName != permission {
  282. return fmt.Errorf("Expected permission %s on repository collaborator, actual permission %s", permission, permName)
  283. }
  284. return nil
  285. }
  286. }
  287. return fmt.Errorf("Repository collaborator did not appear in list of collaborators on repository")
  288. }
  289. }
  290. func testAccGithubRepositoryCollaboratorConfig(repoName, username, permission string) string {
  291. return fmt.Sprintf(`
  292. resource "github_repository" "test" {
  293. name = "%s"
  294. }
  295. resource "github_repository_collaborator" "test_repo_collaborator" {
  296. repository = "${github_repository.test.name}"
  297. username = "%s"
  298. permission = "%s"
  299. }
  300. `, repoName, username, permission)
  301. }
  302. func testAccCheckGithubRepositoryCollaboratorInvited(repoName, username string, invitation *github.RepositoryInvitation) resource.TestCheckFunc {
  303. return func(s *terraform.State) error {
  304. opt := &github.ListOptions{PerPage: maxPerPage}
  305. client := testAccProvider.Meta().(*Owner).v3client
  306. owner := testAccProvider.Meta().(*Owner).name
  307. for {
  308. invitations, resp, err := client.Repositories.ListInvitations(context.TODO(), owner, repoName, opt)
  309. if err != nil {
  310. return errors.New(err.Error())
  311. }
  312. if len(invitations) > 1 {
  313. return fmt.Errorf("multiple invitations have been sent for repository %s", repoName)
  314. }
  315. for _, i := range invitations {
  316. if strings.EqualFold(i.GetInvitee().GetLogin(), username) {
  317. invitation = i
  318. return nil
  319. }
  320. }
  321. if resp.NextPage == 0 {
  322. break
  323. }
  324. opt.Page = resp.NextPage
  325. }
  326. return fmt.Errorf("no invitation found for %s", username)
  327. }
  328. }
  329. func testAccGithubRepositoryCollaboratorTheSame(orig, other *github.RepositoryInvitation) resource.TestCheckFunc {
  330. return func(s *terraform.State) error {
  331. if orig.GetID() != other.GetID() {
  332. return errors.New("collaborators are different")
  333. }
  334. return nil
  335. }
  336. }