@@ -2,22 +2,30 @@ package sync
22
33import (
44 "bytes"
5+ "io/ioutil"
6+ "net/http"
57 "testing"
68
9+ "github.com/cli/cli/context"
10+ "github.com/cli/cli/git"
11+ "github.com/cli/cli/internal/ghrepo"
12+ "github.com/cli/cli/pkg/cmd/repo/sync/syncfakes"
713 "github.com/cli/cli/pkg/cmdutil"
14+ "github.com/cli/cli/pkg/httpmock"
815 "github.com/cli/cli/pkg/iostreams"
16+ "github.com/cli/cli/pkg/prompt"
917 "github.com/google/shlex"
1018 "github.com/stretchr/testify/assert"
1119)
1220
1321func TestNewCmdSync (t * testing.T ) {
1422 tests := []struct {
15- name string
16- tty bool
17- input string
18- output SyncOptions
19- wantsErr bool
20- errMsg string
23+ name string
24+ tty bool
25+ input string
26+ output SyncOptions
27+ wantErr bool
28+ errMsg string
2129 }{
2230 {
2331 name : "no argument" ,
@@ -66,11 +74,11 @@ func TestNewCmdSync(t *testing.T) {
6674 },
6775 },
6876 {
69- name : "notty without confirm" ,
70- tty : false ,
71- input : "" ,
72- wantsErr : true ,
73- errMsg : "`--confirm` required when not running interactively" ,
77+ name : "notty without confirm" ,
78+ tty : false ,
79+ input : "" ,
80+ wantErr : true ,
81+ errMsg : "`--confirm` required when not running interactively" ,
7482 },
7583 }
7684 for _ , tt := range tests {
@@ -94,7 +102,7 @@ func TestNewCmdSync(t *testing.T) {
94102 cmd .SetErr (& bytes.Buffer {})
95103
96104 _ , err = cmd .ExecuteC ()
97- if tt .wantsErr {
105+ if tt .wantErr {
98106 assert .Error (t , err )
99107 assert .Equal (t , tt .errMsg , err .Error ())
100108 return
@@ -111,5 +119,316 @@ func TestNewCmdSync(t *testing.T) {
111119}
112120
113121func Test_SyncRun (t * testing.T ) {
122+ stubConfirm := func (as * prompt.AskStubber ) {
123+ as .StubOne (true )
124+ }
125+
126+ tests := []struct {
127+ name string
128+ tty bool
129+ opts * SyncOptions
130+ remotes []* context.Remote
131+ httpStubs func (* httpmock.Registry )
132+ gitStubs func (* syncfakes.FakeGitClient )
133+ askStubs func (* prompt.AskStubber )
134+ wantStdout string
135+ wantStderr string
136+ wantErr bool
137+ errMsg string
138+ }{
139+ {
140+ name : "sync local repo with parent - tty" ,
141+ tty : true ,
142+ opts : & SyncOptions {},
143+ httpStubs : func (reg * httpmock.Registry ) {
144+ reg .Register (
145+ httpmock .GraphQL (`query RepositoryInfo\b` ),
146+ httpmock .StringResponse (`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}` ))
147+ },
148+ askStubs : stubConfirm ,
149+ wantStdout : "✓ Synced .:trunk from OWNER/REPO:trunk\n " ,
150+ },
151+ {
152+ name : "sync local repo with parent - notty" ,
153+ tty : false ,
154+ opts : & SyncOptions {
155+ SkipConfirm : true ,
156+ },
157+ httpStubs : func (reg * httpmock.Registry ) {
158+ reg .Register (
159+ httpmock .GraphQL (`query RepositoryInfo\b` ),
160+ httpmock .StringResponse (`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}` ))
161+ },
162+ askStubs : stubConfirm ,
163+ wantStdout : "" ,
164+ },
165+ {
166+ name : "sync local repo with specified source repo" ,
167+ tty : true ,
168+ opts : & SyncOptions {
169+ SrcArg : "OWNER2/REPO2" ,
170+ },
171+ httpStubs : func (reg * httpmock.Registry ) {
172+ reg .Register (
173+ httpmock .GraphQL (`query RepositoryInfo\b` ),
174+ httpmock .StringResponse (`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}` ))
175+ },
176+ askStubs : stubConfirm ,
177+ wantStdout : "✓ Synced .:trunk from OWNER2/REPO2:trunk\n " ,
178+ },
179+ {
180+ name : "sync local repo with parent and specified branch" ,
181+ tty : true ,
182+ opts : & SyncOptions {
183+ Branch : "test" ,
184+ },
185+ askStubs : stubConfirm ,
186+ wantStdout : "✓ Synced .:test from OWNER/REPO:test\n " ,
187+ },
188+ {
189+ name : "sync local repo with parent and force specified" ,
190+ tty : true ,
191+ opts : & SyncOptions {
192+ Force : true ,
193+ },
194+ httpStubs : func (reg * httpmock.Registry ) {
195+ reg .Register (
196+ httpmock .GraphQL (`query RepositoryInfo\b` ),
197+ httpmock .StringResponse (`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}` ))
198+ },
199+ gitStubs : func (fgc * syncfakes.FakeGitClient ) {
200+ fgc .HasLocalBranchReturns (true )
201+ fgc .IsAncestorReturns (false , nil )
202+ },
203+ askStubs : stubConfirm ,
204+ wantStderr : "! Using --force will cause diverging commits on .:trunk to be discarded\n " ,
205+ wantStdout : "✓ Synced .:trunk from OWNER/REPO:trunk\n " ,
206+ },
207+ {
208+ name : "sync local repo with parent and not fast forward merge" ,
209+ tty : true ,
210+ opts : & SyncOptions {},
211+ httpStubs : func (reg * httpmock.Registry ) {
212+ reg .Register (
213+ httpmock .GraphQL (`query RepositoryInfo\b` ),
214+ httpmock .StringResponse (`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}` ))
215+ },
216+ gitStubs : func (fgc * syncfakes.FakeGitClient ) {
217+ fgc .HasLocalBranchReturns (true )
218+ fgc .IsAncestorReturns (false , nil )
219+ },
220+ askStubs : stubConfirm ,
221+ wantErr : true ,
222+ errMsg : "can't sync because there are diverging commits, you can use `--force` to overwrite the commits on .:trunk" ,
223+ },
224+ {
225+ name : "sync remote fork with parent - tty" ,
226+ tty : true ,
227+ opts : & SyncOptions {
228+ DestArg : "OWNER/REPO-FORK" ,
229+ },
230+ httpStubs : func (reg * httpmock.Registry ) {
231+ reg .Register (
232+ httpmock .GraphQL (`query RepositoryFindParent\b` ),
233+ httpmock .StringResponse (`{"data":{"repository":{"parent":{"name":"REPO","owner":{"login": "OWNER"}}}}}` ))
234+ reg .Register (
235+ httpmock .GraphQL (`query RepositoryInfo\b` ),
236+ httpmock .StringResponse (`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}` ))
237+ reg .Register (
238+ httpmock .REST ("GET" , "repos/OWNER/REPO/git/refs/heads/trunk" ),
239+ httpmock .StringResponse (`{"object":{"sha":"0xDEADBEEF"}}` ))
240+ reg .Register (
241+ httpmock .REST ("PATCH" , "repos/OWNER/REPO-FORK/git/refs/heads/trunk" ),
242+ httpmock .StringResponse (`{}` ))
243+ },
244+ askStubs : stubConfirm ,
245+ wantStdout : "✓ Synced OWNER/REPO-FORK:trunk from OWNER/REPO:trunk\n " ,
246+ },
247+ {
248+ name : "sync remote fork with parent - notty" ,
249+ tty : false ,
250+ opts : & SyncOptions {
251+ DestArg : "OWNER/REPO-FORK" ,
252+ SkipConfirm : true ,
253+ },
254+ httpStubs : func (reg * httpmock.Registry ) {
255+ reg .Register (
256+ httpmock .GraphQL (`query RepositoryFindParent\b` ),
257+ httpmock .StringResponse (`{"data":{"repository":{"parent":{"name":"REPO","owner":{"login": "OWNER"}}}}}` ))
258+ reg .Register (
259+ httpmock .GraphQL (`query RepositoryInfo\b` ),
260+ httpmock .StringResponse (`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}` ))
261+ reg .Register (
262+ httpmock .REST ("GET" , "repos/OWNER/REPO/git/refs/heads/trunk" ),
263+ httpmock .StringResponse (`{"object":{"sha":"0xDEADBEEF"}}` ))
264+ reg .Register (
265+ httpmock .REST ("PATCH" , "repos/OWNER/REPO-FORK/git/refs/heads/trunk" ),
266+ httpmock .StringResponse (`{}` ))
267+ },
268+ wantStdout : "" ,
269+ },
270+ {
271+ name : "sync remote repo with no parent" ,
272+ tty : true ,
273+ opts : & SyncOptions {
274+ DestArg : "OWNER/REPO" ,
275+ },
276+ httpStubs : func (reg * httpmock.Registry ) {
277+ reg .Register (
278+ httpmock .GraphQL (`query RepositoryFindParent\b` ),
279+ httpmock .StringResponse (`{"data":{"repository":{}}}` ))
280+ },
281+ wantErr : true ,
282+ errMsg : "can't determine source repo for OWNER/REPO because repo is not fork" ,
283+ },
284+ {
285+ name : "sync remote repo with specified source repo" ,
286+ tty : true ,
287+ opts : & SyncOptions {
288+ DestArg : "OWNER/REPO" ,
289+ SrcArg : "OWNER2/REPO2" ,
290+ },
291+ httpStubs : func (reg * httpmock.Registry ) {
292+ reg .Register (
293+ httpmock .GraphQL (`query RepositoryInfo\b` ),
294+ httpmock .StringResponse (`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}` ))
295+ reg .Register (
296+ httpmock .REST ("GET" , "repos/OWNER2/REPO2/git/refs/heads/trunk" ),
297+ httpmock .StringResponse (`{"object":{"sha":"0xDEADBEEF"}}` ))
298+ reg .Register (
299+ httpmock .REST ("PATCH" , "repos/OWNER/REPO/git/refs/heads/trunk" ),
300+ httpmock .StringResponse (`{}` ))
301+ },
302+ askStubs : stubConfirm ,
303+ wantStdout : "✓ Synced OWNER/REPO:trunk from OWNER2/REPO2:trunk\n " ,
304+ },
305+ {
306+ name : "sync remote fork with parent and specified branch" ,
307+ tty : true ,
308+ opts : & SyncOptions {
309+ DestArg : "OWNER/REPO-FORK" ,
310+ Branch : "test" ,
311+ },
312+ httpStubs : func (reg * httpmock.Registry ) {
313+ reg .Register (
314+ httpmock .GraphQL (`query RepositoryFindParent\b` ),
315+ httpmock .StringResponse (`{"data":{"repository":{"parent":{"name":"REPO","owner":{"login": "OWNER"}}}}}` ))
316+ reg .Register (
317+ httpmock .REST ("GET" , "repos/OWNER/REPO/git/refs/heads/test" ),
318+ httpmock .StringResponse (`{"object":{"sha":"0xDEADBEEF"}}` ))
319+ reg .Register (
320+ httpmock .REST ("PATCH" , "repos/OWNER/REPO-FORK/git/refs/heads/test" ),
321+ httpmock .StringResponse (`{}` ))
322+ },
323+ askStubs : stubConfirm ,
324+ wantStdout : "✓ Synced OWNER/REPO-FORK:test from OWNER/REPO:test\n " ,
325+ },
326+ {
327+ name : "sync remote fork with parent and force specified" ,
328+ tty : true ,
329+ opts : & SyncOptions {
330+ DestArg : "OWNER/REPO-FORK" ,
331+ Force : true ,
332+ },
333+ httpStubs : func (reg * httpmock.Registry ) {
334+ reg .Register (
335+ httpmock .GraphQL (`query RepositoryFindParent\b` ),
336+ httpmock .StringResponse (`{"data":{"repository":{"parent":{"name":"REPO","owner":{"login": "OWNER"}}}}}` ))
337+ reg .Register (
338+ httpmock .GraphQL (`query RepositoryInfo\b` ),
339+ httpmock .StringResponse (`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}` ))
340+ reg .Register (
341+ httpmock .REST ("GET" , "repos/OWNER/REPO/git/refs/heads/trunk" ),
342+ httpmock .StringResponse (`{"object":{"sha":"0xDEADBEEF"}}` ))
343+ reg .Register (
344+ httpmock .REST ("PATCH" , "repos/OWNER/REPO-FORK/git/refs/heads/trunk" ),
345+ httpmock .StringResponse (`{}` ))
346+ },
347+ askStubs : stubConfirm ,
348+ wantStderr : "! Using --force will cause diverging commits on OWNER/REPO-FORK:trunk to be discarded\n " ,
349+ wantStdout : "✓ Synced OWNER/REPO-FORK:trunk from OWNER/REPO:trunk\n " ,
350+ },
351+ {
352+ name : "sync remote fork with parent and not fast forward merge" ,
353+ tty : true ,
354+ opts : & SyncOptions {
355+ DestArg : "OWNER/REPO-FORK" ,
356+ },
357+ httpStubs : func (reg * httpmock.Registry ) {
358+ reg .Register (
359+ httpmock .GraphQL (`query RepositoryFindParent\b` ),
360+ httpmock .StringResponse (`{"data":{"repository":{"parent":{"name":"REPO","owner":{"login": "OWNER"}}}}}` ))
361+ reg .Register (
362+ httpmock .GraphQL (`query RepositoryInfo\b` ),
363+ httpmock .StringResponse (`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}` ))
364+ reg .Register (
365+ httpmock .REST ("GET" , "repos/OWNER/REPO/git/refs/heads/trunk" ),
366+ httpmock .StringResponse (`{"object":{"sha":"0xDEADBEEF"}}` ))
367+ reg .Register (
368+ httpmock .REST ("PATCH" , "repos/OWNER/REPO-FORK/git/refs/heads/trunk" ),
369+ func (req * http.Request ) (* http.Response , error ) {
370+ return & http.Response {
371+ StatusCode : 422 ,
372+ Request : req ,
373+ Header : map [string ][]string {"Content-Type" : {"application/json" }},
374+ Body : ioutil .NopCloser (bytes .NewBufferString (`{"message":"Update is not a fast forward"}` )),
375+ }, nil
376+ })
377+ },
378+ askStubs : stubConfirm ,
379+ wantErr : true ,
380+ errMsg : "can't sync because there are diverging commits, you can use `--force` to overwrite the commits on OWNER/REPO-FORK:trunk" ,
381+ },
382+ }
383+ for _ , tt := range tests {
384+ reg := & httpmock.Registry {}
385+ if tt .httpStubs != nil {
386+ tt .httpStubs (reg )
387+ }
388+ tt .opts .HttpClient = func () (* http.Client , error ) {
389+ return & http.Client {Transport : reg }, nil
390+ }
391+
392+ io , _ , stdout , stderr := iostreams .Test ()
393+ io .SetStdinTTY (tt .tty )
394+ io .SetStdoutTTY (tt .tty )
395+ tt .opts .IO = io
396+
397+ tt .opts .BaseRepo = func () (ghrepo.Interface , error ) {
398+ repo , _ := ghrepo .FromFullName ("OWNER/REPO" )
399+ return repo , nil
400+ }
401+
402+ tt .opts .Remotes = func () (context.Remotes , error ) {
403+ if tt .remotes == nil {
404+ return []* context.Remote {{Remote : & git.Remote {Name : "origin" }}}, nil
405+ }
406+ return tt .remotes , nil
407+ }
408+
409+ var gitClient = & syncfakes.FakeGitClient {}
410+ if tt .gitStubs != nil {
411+ tt .gitStubs (gitClient )
412+ }
413+ tt .opts .Git = gitClient
114414
415+ as , teardown := prompt .InitAskStubber ()
416+ defer teardown ()
417+ if tt .askStubs != nil {
418+ tt .askStubs (as )
419+ }
420+
421+ t .Run (tt .name , func (t * testing.T ) {
422+ defer reg .Verify (t )
423+ err := syncRun (tt .opts )
424+ if tt .wantErr {
425+ assert .Error (t , err )
426+ assert .Equal (t , tt .errMsg , err .Error ())
427+ return
428+ }
429+ assert .NoError (t , err )
430+ assert .Equal (t , tt .wantStderr , stderr .String ())
431+ assert .Equal (t , tt .wantStdout , stdout .String ())
432+ })
433+ }
115434}
0 commit comments