You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

611 lines
17 KiB

  1. package models
  2. import (
  3. "fmt"
  4. "path"
  5. "strings"
  6. "testing"
  7. "code.gitea.io/gitea/modules/git"
  8. "code.gitea.io/gitea/modules/setting"
  9. "github.com/stretchr/testify/assert"
  10. )
  11. func TestAction_GetRepoPath(t *testing.T) {
  12. assert.NoError(t, PrepareTestDatabase())
  13. repo := AssertExistsAndLoadBean(t, &Repository{}).(*Repository)
  14. owner := AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User)
  15. action := &Action{RepoID: repo.ID}
  16. assert.Equal(t, path.Join(owner.Name, repo.Name), action.GetRepoPath())
  17. }
  18. func TestAction_GetRepoLink(t *testing.T) {
  19. assert.NoError(t, PrepareTestDatabase())
  20. repo := AssertExistsAndLoadBean(t, &Repository{}).(*Repository)
  21. owner := AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User)
  22. action := &Action{RepoID: repo.ID}
  23. setting.AppSubURL = "/suburl/"
  24. expected := path.Join(setting.AppSubURL, owner.Name, repo.Name)
  25. assert.Equal(t, expected, action.GetRepoLink())
  26. }
  27. func TestNewRepoAction(t *testing.T) {
  28. assert.NoError(t, PrepareTestDatabase())
  29. user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
  30. repo := AssertExistsAndLoadBean(t, &Repository{OwnerID: user.ID}).(*Repository)
  31. repo.Owner = user
  32. actionBean := &Action{
  33. OpType: ActionCreateRepo,
  34. ActUserID: user.ID,
  35. RepoID: repo.ID,
  36. ActUser: user,
  37. Repo: repo,
  38. IsPrivate: repo.IsPrivate,
  39. }
  40. AssertNotExistsBean(t, actionBean)
  41. assert.NoError(t, NewRepoAction(user, repo))
  42. AssertExistsAndLoadBean(t, actionBean)
  43. CheckConsistencyFor(t, &Action{})
  44. }
  45. func TestRenameRepoAction(t *testing.T) {
  46. assert.NoError(t, PrepareTestDatabase())
  47. user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
  48. repo := AssertExistsAndLoadBean(t, &Repository{OwnerID: user.ID}).(*Repository)
  49. repo.Owner = user
  50. oldRepoName := repo.Name
  51. const newRepoName = "newRepoName"
  52. repo.Name = newRepoName
  53. repo.LowerName = strings.ToLower(newRepoName)
  54. actionBean := &Action{
  55. OpType: ActionRenameRepo,
  56. ActUserID: user.ID,
  57. ActUser: user,
  58. RepoID: repo.ID,
  59. Repo: repo,
  60. IsPrivate: repo.IsPrivate,
  61. Content: oldRepoName,
  62. }
  63. AssertNotExistsBean(t, actionBean)
  64. assert.NoError(t, RenameRepoAction(user, oldRepoName, repo))
  65. AssertExistsAndLoadBean(t, actionBean)
  66. _, err := x.ID(repo.ID).Cols("name", "lower_name").Update(repo)
  67. assert.NoError(t, err)
  68. CheckConsistencyFor(t, &Action{})
  69. }
  70. func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
  71. pushCommits := NewPushCommits()
  72. pushCommits.Commits = []*PushCommit{
  73. {
  74. Sha1: "abcdef1",
  75. CommitterEmail: "user2@example.com",
  76. CommitterName: "User Two",
  77. AuthorEmail: "user4@example.com",
  78. AuthorName: "User Four",
  79. Message: "message1",
  80. },
  81. {
  82. Sha1: "abcdef2",
  83. CommitterEmail: "user2@example.com",
  84. CommitterName: "User Two",
  85. AuthorEmail: "user2@example.com",
  86. AuthorName: "User Two",
  87. Message: "message2",
  88. },
  89. }
  90. pushCommits.Len = len(pushCommits.Commits)
  91. payloadCommits := pushCommits.ToAPIPayloadCommits("/username/reponame")
  92. if assert.Len(t, payloadCommits, 2) {
  93. assert.Equal(t, "abcdef1", payloadCommits[0].ID)
  94. assert.Equal(t, "message1", payloadCommits[0].Message)
  95. assert.Equal(t, "/username/reponame/commit/abcdef1", payloadCommits[0].URL)
  96. assert.Equal(t, "User Two", payloadCommits[0].Committer.Name)
  97. assert.Equal(t, "user2", payloadCommits[0].Committer.UserName)
  98. assert.Equal(t, "User Four", payloadCommits[0].Author.Name)
  99. assert.Equal(t, "user4", payloadCommits[0].Author.UserName)
  100. assert.Equal(t, "abcdef2", payloadCommits[1].ID)
  101. assert.Equal(t, "message2", payloadCommits[1].Message)
  102. assert.Equal(t, "/username/reponame/commit/abcdef2", payloadCommits[1].URL)
  103. assert.Equal(t, "User Two", payloadCommits[1].Committer.Name)
  104. assert.Equal(t, "user2", payloadCommits[1].Committer.UserName)
  105. assert.Equal(t, "User Two", payloadCommits[1].Author.Name)
  106. assert.Equal(t, "user2", payloadCommits[1].Author.UserName)
  107. }
  108. }
  109. func TestPushCommits_AvatarLink(t *testing.T) {
  110. pushCommits := NewPushCommits()
  111. pushCommits.Commits = []*PushCommit{
  112. {
  113. Sha1: "abcdef1",
  114. CommitterEmail: "user2@example.com",
  115. CommitterName: "User Two",
  116. AuthorEmail: "user4@example.com",
  117. AuthorName: "User Four",
  118. Message: "message1",
  119. },
  120. {
  121. Sha1: "abcdef2",
  122. CommitterEmail: "user2@example.com",
  123. CommitterName: "User Two",
  124. AuthorEmail: "user2@example.com",
  125. AuthorName: "User Two",
  126. Message: "message2",
  127. },
  128. }
  129. pushCommits.Len = len(pushCommits.Commits)
  130. assert.Equal(t,
  131. "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon",
  132. pushCommits.AvatarLink("user2@example.com"))
  133. assert.Equal(t,
  134. "https://secure.gravatar.com/avatar/19ade630b94e1e0535b3df7387434154?d=identicon",
  135. pushCommits.AvatarLink("nonexistent@example.com"))
  136. }
  137. func Test_getIssueFromRef(t *testing.T) {
  138. assert.NoError(t, PrepareTestDatabase())
  139. repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
  140. for _, test := range []struct {
  141. Ref string
  142. ExpectedIssueID int64
  143. }{
  144. {"#2", 2},
  145. {"reopen #2", 2},
  146. {"user2/repo2#1", 4},
  147. {"fixes user2/repo2#1", 4},
  148. {"fixes: user2/repo2#1", 4},
  149. } {
  150. issue, err := getIssueFromRef(repo, test.Ref)
  151. assert.NoError(t, err)
  152. if assert.NotNil(t, issue) {
  153. assert.EqualValues(t, test.ExpectedIssueID, issue.ID)
  154. }
  155. }
  156. for _, badRef := range []string{
  157. "doesnotexist/doesnotexist#1",
  158. fmt.Sprintf("#%d", NonexistentID),
  159. } {
  160. issue, err := getIssueFromRef(repo, badRef)
  161. assert.NoError(t, err)
  162. assert.Nil(t, issue)
  163. }
  164. }
  165. func TestUpdateIssuesCommit(t *testing.T) {
  166. assert.NoError(t, PrepareTestDatabase())
  167. pushCommits := []*PushCommit{
  168. {
  169. Sha1: "abcdef1",
  170. CommitterEmail: "user2@example.com",
  171. CommitterName: "User Two",
  172. AuthorEmail: "user4@example.com",
  173. AuthorName: "User Four",
  174. Message: "start working on #FST-1, #1",
  175. },
  176. {
  177. Sha1: "abcdef2",
  178. CommitterEmail: "user2@example.com",
  179. CommitterName: "User Two",
  180. AuthorEmail: "user2@example.com",
  181. AuthorName: "User Two",
  182. Message: "a plain message",
  183. },
  184. {
  185. Sha1: "abcdef2",
  186. CommitterEmail: "user2@example.com",
  187. CommitterName: "User Two",
  188. AuthorEmail: "user2@example.com",
  189. AuthorName: "User Two",
  190. Message: "close #2",
  191. },
  192. }
  193. user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
  194. repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
  195. repo.Owner = user
  196. commentBean := &Comment{
  197. Type: CommentTypeCommitRef,
  198. CommitSHA: "abcdef1",
  199. PosterID: user.ID,
  200. IssueID: 1,
  201. }
  202. issueBean := &Issue{RepoID: repo.ID, Index: 2}
  203. AssertNotExistsBean(t, commentBean)
  204. AssertNotExistsBean(t, &Issue{RepoID: repo.ID, Index: 2}, "is_closed=1")
  205. assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch))
  206. AssertExistsAndLoadBean(t, commentBean)
  207. AssertExistsAndLoadBean(t, issueBean, "is_closed=1")
  208. CheckConsistencyFor(t, &Action{})
  209. // Test that push to a non-default branch closes no issue.
  210. pushCommits = []*PushCommit{
  211. {
  212. Sha1: "abcdef1",
  213. CommitterEmail: "user2@example.com",
  214. CommitterName: "User Two",
  215. AuthorEmail: "user4@example.com",
  216. AuthorName: "User Four",
  217. Message: "close #1",
  218. },
  219. }
  220. repo = AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
  221. commentBean = &Comment{
  222. Type: CommentTypeCommitRef,
  223. CommitSHA: "abcdef1",
  224. PosterID: user.ID,
  225. IssueID: 6,
  226. }
  227. issueBean = &Issue{RepoID: repo.ID, Index: 1}
  228. AssertNotExistsBean(t, commentBean)
  229. AssertNotExistsBean(t, &Issue{RepoID: repo.ID, Index: 1}, "is_closed=1")
  230. assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, "non-existing-branch"))
  231. AssertExistsAndLoadBean(t, commentBean)
  232. AssertNotExistsBean(t, issueBean, "is_closed=1")
  233. CheckConsistencyFor(t, &Action{})
  234. }
  235. func TestUpdateIssuesCommit_Colon(t *testing.T) {
  236. assert.NoError(t, PrepareTestDatabase())
  237. pushCommits := []*PushCommit{
  238. {
  239. Sha1: "abcdef2",
  240. CommitterEmail: "user2@example.com",
  241. CommitterName: "User Two",
  242. AuthorEmail: "user2@example.com",
  243. AuthorName: "User Two",
  244. Message: "close: #2",
  245. },
  246. }
  247. user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
  248. repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
  249. repo.Owner = user
  250. issueBean := &Issue{RepoID: repo.ID, Index: 2}
  251. AssertNotExistsBean(t, &Issue{RepoID: repo.ID, Index: 2}, "is_closed=1")
  252. assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch))
  253. AssertExistsAndLoadBean(t, issueBean, "is_closed=1")
  254. CheckConsistencyFor(t, &Action{})
  255. }
  256. func TestUpdateIssuesCommit_Issue5957(t *testing.T) {
  257. assert.NoError(t, PrepareTestDatabase())
  258. user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
  259. // Test that push to a non-default branch closes an issue.
  260. pushCommits := []*PushCommit{
  261. {
  262. Sha1: "abcdef1",
  263. CommitterEmail: "user2@example.com",
  264. CommitterName: "User Two",
  265. AuthorEmail: "user4@example.com",
  266. AuthorName: "User Four",
  267. Message: "close #2",
  268. },
  269. }
  270. repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository)
  271. commentBean := &Comment{
  272. Type: CommentTypeCommitRef,
  273. CommitSHA: "abcdef1",
  274. PosterID: user.ID,
  275. IssueID: 7,
  276. }
  277. issueBean := &Issue{RepoID: repo.ID, Index: 2, ID: 7}
  278. AssertNotExistsBean(t, commentBean)
  279. AssertNotExistsBean(t, issueBean, "is_closed=1")
  280. assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, "non-existing-branch"))
  281. AssertExistsAndLoadBean(t, commentBean)
  282. AssertExistsAndLoadBean(t, issueBean, "is_closed=1")
  283. CheckConsistencyFor(t, &Action{})
  284. }
  285. func TestUpdateIssuesCommit_AnotherRepo(t *testing.T) {
  286. assert.NoError(t, PrepareTestDatabase())
  287. user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
  288. // Test that a push to default branch closes issue in another repo
  289. // If the user also has push permissions to that repo
  290. pushCommits := []*PushCommit{
  291. {
  292. Sha1: "abcdef1",
  293. CommitterEmail: "user2@example.com",
  294. CommitterName: "User Two",
  295. AuthorEmail: "user2@example.com",
  296. AuthorName: "User Two",
  297. Message: "close user2/repo1#1",
  298. },
  299. }
  300. repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository)
  301. commentBean := &Comment{
  302. Type: CommentTypeCommitRef,
  303. CommitSHA: "abcdef1",
  304. PosterID: user.ID,
  305. IssueID: 1,
  306. }
  307. issueBean := &Issue{RepoID: 1, Index: 1, ID: 1}
  308. AssertNotExistsBean(t, commentBean)
  309. AssertNotExistsBean(t, issueBean, "is_closed=1")
  310. assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch))
  311. AssertExistsAndLoadBean(t, commentBean)
  312. AssertExistsAndLoadBean(t, issueBean, "is_closed=1")
  313. CheckConsistencyFor(t, &Action{})
  314. }
  315. func TestUpdateIssuesCommit_AnotherRepoNoPermission(t *testing.T) {
  316. assert.NoError(t, PrepareTestDatabase())
  317. user := AssertExistsAndLoadBean(t, &User{ID: 10}).(*User)
  318. // Test that a push with close reference *can not* close issue
  319. // If the commiter doesn't have push rights in that repo
  320. pushCommits := []*PushCommit{
  321. {
  322. Sha1: "abcdef3",
  323. CommitterEmail: "user10@example.com",
  324. CommitterName: "User Ten",
  325. AuthorEmail: "user10@example.com",
  326. AuthorName: "User Ten",
  327. Message: "close user3/repo3#1",
  328. },
  329. }
  330. repo := AssertExistsAndLoadBean(t, &Repository{ID: 6}).(*Repository)
  331. commentBean := &Comment{
  332. Type: CommentTypeCommitRef,
  333. CommitSHA: "abcdef3",
  334. PosterID: user.ID,
  335. IssueID: 6,
  336. }
  337. issueBean := &Issue{RepoID: 3, Index: 1, ID: 6}
  338. AssertNotExistsBean(t, commentBean)
  339. AssertNotExistsBean(t, issueBean, "is_closed=1")
  340. assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch))
  341. AssertExistsAndLoadBean(t, commentBean)
  342. AssertNotExistsBean(t, issueBean, "is_closed=1")
  343. CheckConsistencyFor(t, &Action{})
  344. }
  345. func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *Action) {
  346. AssertNotExistsBean(t, actionBean)
  347. assert.NoError(t, CommitRepoAction(opts))
  348. AssertExistsAndLoadBean(t, actionBean)
  349. CheckConsistencyFor(t, &Action{})
  350. }
  351. func TestCommitRepoAction(t *testing.T) {
  352. samples := []struct {
  353. userID int64
  354. repositoryID int64
  355. commitRepoActionOptions CommitRepoActionOptions
  356. action Action
  357. }{
  358. {
  359. userID: 2,
  360. repositoryID: 2,
  361. commitRepoActionOptions: CommitRepoActionOptions{
  362. RefFullName: "refName",
  363. OldCommitID: "oldCommitID",
  364. NewCommitID: "newCommitID",
  365. Commits: &PushCommits{
  366. avatars: make(map[string]string),
  367. Commits: []*PushCommit{
  368. {
  369. Sha1: "abcdef1",
  370. CommitterEmail: "user2@example.com",
  371. CommitterName: "User Two",
  372. AuthorEmail: "user4@example.com",
  373. AuthorName: "User Four",
  374. Message: "message1",
  375. },
  376. {
  377. Sha1: "abcdef2",
  378. CommitterEmail: "user2@example.com",
  379. CommitterName: "User Two",
  380. AuthorEmail: "user2@example.com",
  381. AuthorName: "User Two",
  382. Message: "message2",
  383. },
  384. },
  385. Len: 2,
  386. },
  387. },
  388. action: Action{
  389. OpType: ActionCommitRepo,
  390. RefName: "refName",
  391. },
  392. },
  393. {
  394. userID: 2,
  395. repositoryID: 1,
  396. commitRepoActionOptions: CommitRepoActionOptions{
  397. RefFullName: git.TagPrefix + "v1.1",
  398. OldCommitID: git.EmptySHA,
  399. NewCommitID: "newCommitID",
  400. Commits: &PushCommits{},
  401. },
  402. action: Action{
  403. OpType: ActionPushTag,
  404. RefName: "v1.1",
  405. },
  406. },
  407. {
  408. userID: 2,
  409. repositoryID: 1,
  410. commitRepoActionOptions: CommitRepoActionOptions{
  411. RefFullName: git.TagPrefix + "v1.1",
  412. OldCommitID: "oldCommitID",
  413. NewCommitID: git.EmptySHA,
  414. Commits: &PushCommits{},
  415. },
  416. action: Action{
  417. OpType: ActionDeleteTag,
  418. RefName: "v1.1",
  419. },
  420. },
  421. {
  422. userID: 2,
  423. repositoryID: 1,
  424. commitRepoActionOptions: CommitRepoActionOptions{
  425. RefFullName: git.BranchPrefix + "feature/1",
  426. OldCommitID: "oldCommitID",
  427. NewCommitID: git.EmptySHA,
  428. Commits: &PushCommits{},
  429. },
  430. action: Action{
  431. OpType: ActionDeleteBranch,
  432. RefName: "feature/1",
  433. },
  434. },
  435. }
  436. for _, s := range samples {
  437. PrepareTestEnv(t)
  438. user := AssertExistsAndLoadBean(t, &User{ID: s.userID}).(*User)
  439. repo := AssertExistsAndLoadBean(t, &Repository{ID: s.repositoryID, OwnerID: user.ID}).(*Repository)
  440. repo.Owner = user
  441. s.commitRepoActionOptions.PusherName = user.Name
  442. s.commitRepoActionOptions.RepoOwnerID = user.ID
  443. s.commitRepoActionOptions.RepoName = repo.Name
  444. s.action.ActUserID = user.ID
  445. s.action.RepoID = repo.ID
  446. s.action.Repo = repo
  447. s.action.IsPrivate = repo.IsPrivate
  448. testCorrectRepoAction(t, s.commitRepoActionOptions, &s.action)
  449. }
  450. }
  451. func TestTransferRepoAction(t *testing.T) {
  452. assert.NoError(t, PrepareTestDatabase())
  453. user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
  454. user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
  455. repo := AssertExistsAndLoadBean(t, &Repository{ID: 1, OwnerID: user2.ID}).(*Repository)
  456. repo.OwnerID = user4.ID
  457. repo.Owner = user4
  458. actionBean := &Action{
  459. OpType: ActionTransferRepo,
  460. ActUserID: user2.ID,
  461. ActUser: user2,
  462. RepoID: repo.ID,
  463. Repo: repo,
  464. IsPrivate: repo.IsPrivate,
  465. }
  466. AssertNotExistsBean(t, actionBean)
  467. assert.NoError(t, TransferRepoAction(user2, user2, repo))
  468. AssertExistsAndLoadBean(t, actionBean)
  469. _, err := x.ID(repo.ID).Cols("owner_id").Update(repo)
  470. assert.NoError(t, err)
  471. CheckConsistencyFor(t, &Action{})
  472. }
  473. func TestMergePullRequestAction(t *testing.T) {
  474. assert.NoError(t, PrepareTestDatabase())
  475. user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
  476. repo := AssertExistsAndLoadBean(t, &Repository{ID: 1, OwnerID: user.ID}).(*Repository)
  477. repo.Owner = user
  478. issue := AssertExistsAndLoadBean(t, &Issue{ID: 3, RepoID: repo.ID}).(*Issue)
  479. actionBean := &Action{
  480. OpType: ActionMergePullRequest,
  481. ActUserID: user.ID,
  482. ActUser: user,
  483. RepoID: repo.ID,
  484. Repo: repo,
  485. IsPrivate: repo.IsPrivate,
  486. }
  487. AssertNotExistsBean(t, actionBean)
  488. assert.NoError(t, MergePullRequestAction(user, repo, issue))
  489. AssertExistsAndLoadBean(t, actionBean)
  490. CheckConsistencyFor(t, &Action{})
  491. }
  492. func TestGetFeeds(t *testing.T) {
  493. // test with an individual user
  494. assert.NoError(t, PrepareTestDatabase())
  495. user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
  496. actions, err := GetFeeds(GetFeedsOptions{
  497. RequestedUser: user,
  498. RequestingUserID: user.ID,
  499. IncludePrivate: true,
  500. OnlyPerformedBy: false,
  501. IncludeDeleted: true,
  502. })
  503. assert.NoError(t, err)
  504. if assert.Len(t, actions, 1) {
  505. assert.EqualValues(t, 1, actions[0].ID)
  506. assert.EqualValues(t, user.ID, actions[0].UserID)
  507. }
  508. actions, err = GetFeeds(GetFeedsOptions{
  509. RequestedUser: user,
  510. RequestingUserID: user.ID,
  511. IncludePrivate: false,
  512. OnlyPerformedBy: false,
  513. })
  514. assert.NoError(t, err)
  515. assert.Len(t, actions, 0)
  516. }
  517. func TestGetFeeds2(t *testing.T) {
  518. // test with an organization user
  519. assert.NoError(t, PrepareTestDatabase())
  520. org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
  521. const userID = 2 // user2 is an owner of the organization
  522. actions, err := GetFeeds(GetFeedsOptions{
  523. RequestedUser: org,
  524. RequestingUserID: userID,
  525. IncludePrivate: true,
  526. OnlyPerformedBy: false,
  527. IncludeDeleted: true,
  528. })
  529. assert.NoError(t, err)
  530. assert.Len(t, actions, 1)
  531. if assert.Len(t, actions, 1) {
  532. assert.EqualValues(t, 2, actions[0].ID)
  533. assert.EqualValues(t, org.ID, actions[0].UserID)
  534. }
  535. actions, err = GetFeeds(GetFeedsOptions{
  536. RequestedUser: org,
  537. RequestingUserID: userID,
  538. IncludePrivate: false,
  539. OnlyPerformedBy: false,
  540. IncludeDeleted: true,
  541. })
  542. assert.NoError(t, err)
  543. assert.Len(t, actions, 0)
  544. }