main
rb 143 lines 5.27 KB
Raw
1 # frozen_string_literal: true
2
3 require "rails_helper"
4 require "tmpdir"
5 require "fileutils"
6
7 RSpec.describe "Pull requests", type: :request do
8 around do |example|
9 Dir.mktmpdir("pull-requests-spec") do |tmp|
10 @repo_dir = File.join(tmp, "demo.git")
11 build_repo(@repo_dir)
12 example.run
13 end
14 end
15
16 let(:owner) { User.create!(smbcloud_id: 9001, email: "pr-owner@example.com", username: "prowner") }
17 let(:outsider) { User.create!(smbcloud_id: 9002, email: "pr-outsider@example.com", username: "proutsider") }
18 let!(:repo) do
19 owner.repositories.create!(name: "demo", kind: "code", default_branch: "main", disk_path: @repo_dir)
20 end
21
22 # Sign-in runs through smbCloud's native gem in production, so request specs
23 # stub current_user rather than log in for real (same pattern as admin_spec.rb).
24 def sign_in(user)
25 allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user)
26 end
27
28 describe "GET /:username/:repository/pulls" do
29 it "lists open pull requests by default" do
30 repo.pull_requests.create!(user: owner, number: 1, title: "Add feature", head_ref: "feature", base_ref: "main")
31
32 get "/prowner/demo/pulls"
33
34 expect(response).to have_http_status(:success)
35 expect(response.body).to include("Add feature")
36 end
37
38 it "is reachable without signing in for a public repo" do
39 get "/prowner/demo/pulls"
40 expect(response).to have_http_status(:success)
41 end
42 end
43
44 describe "GET /:username/:repository/pull/:number" do
45 it "shows the PR with its commits, diff, and comments" do
46 pr = repo.pull_requests.create!(user: owner, number: 1, title: "Add feature", body: "Why this matters", head_ref: "feature", base_ref: "main")
47 pr.comments.create!(user: owner, body: "Looks good")
48
49 get "/prowner/demo/pull/1"
50
51 expect(response).to have_http_status(:success)
52 expect(response.body).to include("Add feature")
53 expect(response.body).to include("feature.txt")
54 expect(response.body).to include("Looks good")
55 end
56 end
57
58 describe "POST /:username/:repository/pulls" do
59 it "lets the owner open a pull request" do
60 sign_in(owner)
61
62 post "/prowner/demo/pulls", params: { pull_request: { title: "Add feature", head_ref: "feature", base_ref: "main" } }
63
64 expect(response).to redirect_to("/prowner/demo/pull/1")
65 expect(repo.pull_requests.count).to eq(1)
66 end
67
68 it "refuses a non-owner" do
69 sign_in(outsider)
70
71 post "/prowner/demo/pulls", params: { pull_request: { title: "Add feature", head_ref: "feature", base_ref: "main" } }
72
73 expect(repo.pull_requests.count).to eq(0)
74 end
75 end
76
77 describe "POST /:username/:repository/pull/:number/merge" do
78 it "merges a mergeable PR for the owner" do
79 pr = repo.pull_requests.create!(user: owner, number: 1, title: "Add feature", head_ref: "feature", base_ref: "main")
80 sign_in(owner)
81
82 post "/prowner/demo/pull/1/merge"
83
84 expect(response).to redirect_to("/prowner/demo/pull/1")
85 expect(pr.reload.state).to eq("merged")
86 end
87
88 it "refuses a non-owner" do
89 pr = repo.pull_requests.create!(user: owner, number: 1, title: "Add feature", head_ref: "feature", base_ref: "main")
90 sign_in(outsider)
91
92 post "/prowner/demo/pull/1/merge"
93
94 expect(pr.reload.state).to eq("open")
95 end
96 end
97
98 describe "POST /:username/:repository/pull/:number/close and /reopen" do
99 it "closes then reopens a PR for the owner" do
100 pr = repo.pull_requests.create!(user: owner, number: 1, title: "Add feature", head_ref: "feature", base_ref: "main")
101 sign_in(owner)
102
103 post "/prowner/demo/pull/1/close"
104 expect(pr.reload.state).to eq("closed")
105
106 post "/prowner/demo/pull/1/reopen"
107 expect(pr.reload.state).to eq("open")
108 end
109 end
110
111 describe "POST /:username/:repository/pull/:number/comments" do
112 it "lets a signed-in reader comment" do
113 repo.pull_requests.create!(user: owner, number: 1, title: "Add feature", head_ref: "feature", base_ref: "main")
114 sign_in(outsider)
115
116 post "/prowner/demo/pull/1/comments", params: { body: "Nice work" }
117
118 expect(response).to redirect_to("/prowner/demo/pull/1")
119 expect(Comment.last.body).to eq("Nice work")
120 end
121 end
122
123 def build_repo(bare_path)
124 FileUtils.mkdir_p(bare_path)
125 system("git", "init", "--bare", bare_path, exception: true)
126 Dir.mktmpdir do |work|
127 system("git", "-C", work, "init", "-b", "main", exception: true)
128 system("git", "-C", work, "config", "user.email", "t@example.com", exception: true)
129 system("git", "-C", work, "config", "user.name", "Test", exception: true)
130 File.write(File.join(work, "README.md"), "# Demo\n")
131 system("git", "-C", work, "add", ".", exception: true)
132 system("git", "-C", work, "commit", "-m", "seed", exception: true)
133 system("git", "-C", work, "remote", "add", "origin", bare_path, exception: true)
134 system("git", "-C", work, "push", "origin", "main", exception: true)
135
136 system("git", "-C", work, "checkout", "-b", "feature", exception: true)
137 File.write(File.join(work, "feature.txt"), "hi\n")
138 system("git", "-C", work, "add", ".", exception: true)
139 system("git", "-C", work, "commit", "-m", "feature commit", exception: true)
140 system("git", "-C", work, "push", "origin", "feature", exception: true)
141 end
142 end
143 end