package edit import ( "bytes" "context" "io" "net/http" "testing" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/httpmock" "github.com/cli/cli/v2/pkg/iostreams" "github.com/google/shlex" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewCmdEdit(t *testing.T) { tests := []struct { name string args string wantOpts EditOptions wantErr string }{ { name: "change repo description", args: "--description hello", wantOpts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{ Description: sp("hello"), }, }, }, { name: "deny public visibility change without accepting consequences", args: "--visibility public", wantOpts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{}, }, wantErr: "use of --visibility flag requires --accept-visibility-change-consequences flag", }, { name: "allow public visibility change with accepting consequences", args: "--visibility public --accept-visibility-change-consequences", wantOpts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{ Visibility: sp("public"), }, }, }, { name: "deny private visibility change without accepting consequences", args: "--visibility private", wantOpts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{}, }, wantErr: "use of --visibility flag requires --accept-visibility-change-consequences flag", }, { name: "allow private visibility change with accepting consequences", args: "--visibility private --accept-visibility-change-consequences", wantOpts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{ Visibility: sp("private"), }, }, }, { name: "deny internal visibility change without accepting consequences", args: "--visibility internal", wantOpts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{}, }, wantErr: "use of --visibility flag requires --accept-visibility-change-consequences flag", }, { name: "allow internal visibility change with accepting consequences", args: "--visibility internal --accept-visibility-change-consequences", wantOpts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{ Visibility: sp("internal"), }, }, }, { name: "squash merge commit message with enable-squash-merge", args: "--enable-squash-merge --squash-merge-commit-message pr-title", wantOpts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{ squashMergeCommitMsg: sp("pr-title"), EnableSquashMerge: bp(true), SquashMergeCommitTitle: sp("PR_TITLE"), SquashMergeCommitMessage: sp("BLANK"), }, }, }, { name: "squash merge commit message without enable-squash-merge", args: "--squash-merge-commit-message default", wantErr: "--squash-merge-commit-message requires --enable-squash-merge", }, { name: "squash merge commit message with invalid value", args: "--enable-squash-merge --squash-merge-commit-message blah", wantErr: `invalid value for --squash-merge-commit-message: "blah". Valid values are: default, pr-title, pr-title-commits, pr-title-description`, }, { name: "squash merge commit message with enable-squash-merge=false", args: "--enable-squash-merge=false --squash-merge-commit-message default", wantErr: "--squash-merge-commit-message cannot be used when --enable-squash-merge=false", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ios, _, _, _ := iostreams.Test() ios.SetStdoutTTY(true) ios.SetStdinTTY(true) ios.SetStderrTTY(true) f := &cmdutil.Factory{ IOStreams: ios, BaseRepo: func() (ghrepo.Interface, error) { return ghrepo.New("OWNER", "REPO"), nil }, HttpClient: func() (*http.Client, error) { return nil, nil }, } argv, err := shlex.Split(tt.args) assert.NoError(t, err) var gotOpts *EditOptions cmd := NewCmdEdit(f, func(opts *EditOptions) error { gotOpts = opts return nil }) cmd.Flags().BoolP("help", "x", false, "") cmd.SetArgs(argv) cmd.SetIn(&bytes.Buffer{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) _, err = cmd.ExecuteC() if tt.wantErr != "" { assert.EqualError(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, ghrepo.FullName(tt.wantOpts.Repository), ghrepo.FullName(gotOpts.Repository)) assert.Equal(t, tt.wantOpts.Edits, gotOpts.Edits) }) } } func Test_editRun(t *testing.T) { tests := []struct { name string opts EditOptions httpStubs func(*testing.T, *httpmock.Registry) wantsStderr string wantsErr string }{ { name: "change name and description", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{ Homepage: sp("newURL"), Description: sp("hello world!"), }, }, httpStubs: func(t *testing.T, r *httpmock.Registry) { r.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, 2, len(payload)) assert.Equal(t, "newURL", payload["homepage"]) assert.Equal(t, "hello world!", payload["description"]) })) }, }, { name: "add and remove topics", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), AddTopics: []string{"topic1", "topic2"}, RemoveTopics: []string{"topic3"}, }, httpStubs: func(t *testing.T, r *httpmock.Registry) { r.Register( httpmock.REST("GET", "repos/OWNER/REPO/topics"), httpmock.StringResponse(`{"names":["topic2", "topic3", "go"]}`)) r.Register( httpmock.REST("PUT", "repos/OWNER/REPO/topics"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, 1, len(payload)) assert.Equal(t, []interface{}{"topic2", "go", "topic1"}, payload["names"]) })) }, }, { name: "allow update branch", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{ AllowUpdateBranch: bp(true), }, }, httpStubs: func(t *testing.T, r *httpmock.Registry) { r.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, 1, len(payload)) assert.Equal(t, true, payload["allow_update_branch"]) })) }, }, { name: "enable/disable security and analysis settings", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{ SecurityAndAnalysis: &SecurityAndAnalysisInput{ EnableAdvancedSecurity: &SecurityAndAnalysisStatus{ Status: sp("enabled"), }, EnableSecretScanning: &SecurityAndAnalysisStatus{ Status: sp("enabled"), }, EnableSecretScanningPushProtection: &SecurityAndAnalysisStatus{ Status: sp("disabled"), }, }, }, }, httpStubs: func(t *testing.T, r *httpmock.Registry) { r.Register( httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(`{"data": { "repository": { "viewerCanAdminister": true } } }`)) r.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, 1, len(payload)) securityAndAnalysis := payload["security_and_analysis"].(map[string]interface{}) assert.Equal(t, "enabled", securityAndAnalysis["advanced_security"].(map[string]interface{})["status"]) assert.Equal(t, "enabled", securityAndAnalysis["secret_scanning"].(map[string]interface{})["status"]) assert.Equal(t, "disabled", securityAndAnalysis["secret_scanning_push_protection"].(map[string]interface{})["status"]) })) }, }, { name: "set squash merge commit message to pr-title-description", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{ EnableSquashMerge: bp(true), SquashMergeCommitTitle: sp("PR_TITLE"), SquashMergeCommitMessage: sp("PR_BODY"), }, }, httpStubs: func(t *testing.T, r *httpmock.Registry) { r.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, true, payload["allow_squash_merge"]) assert.Equal(t, "PR_TITLE", payload["squash_merge_commit_title"]) assert.Equal(t, "PR_BODY", payload["squash_merge_commit_message"]) })) }, }, { name: "does not have sufficient permissions for security edits", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), Edits: EditRepositoryInput{ SecurityAndAnalysis: &SecurityAndAnalysisInput{ EnableAdvancedSecurity: &SecurityAndAnalysisStatus{ Status: sp("enabled"), }, EnableSecretScanning: &SecurityAndAnalysisStatus{ Status: sp("enabled"), }, EnableSecretScanningPushProtection: &SecurityAndAnalysisStatus{ Status: sp("disabled"), }, }, }, }, httpStubs: func(t *testing.T, r *httpmock.Registry) { r.Register( httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(`{"data": { "repository": { "viewerCanAdminister": false } } }`)) }, wantsErr: "you do not have sufficient permissions to edit repository security and analysis features", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ios, _, _, _ := iostreams.Test() ios.SetStdoutTTY(true) ios.SetStdinTTY(true) ios.SetStderrTTY(true) httpReg := &httpmock.Registry{} defer httpReg.Verify(t) if tt.httpStubs != nil { tt.httpStubs(t, httpReg) } opts := &tt.opts opts.HTTPClient = &http.Client{Transport: httpReg} opts.IO = ios err := editRun(context.Background(), opts) if tt.wantsErr == "" { require.NoError(t, err) } else { assert.EqualError(t, err, tt.wantsErr) return } }) } } func Test_editRun_interactive(t *testing.T) { editList := []string{ "Default Branch Name", "Description", "Home Page URL", "Issues", "Merge Options", "Projects", "Template Repository", "Topics", "Visibility", "Wikis"} tests := []struct { name string opts EditOptions promptStubs func(*prompter.MockPrompter) httpStubs func(*testing.T, *httpmock.Registry) wantsStderr string wantsErr string }{ { name: "forking of org repo", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), InteractiveMode: true, }, promptStubs: func(pm *prompter.MockPrompter) { el := append(editList, optionAllowForking) pm.RegisterMultiSelect("What do you want to edit?", nil, el, func(_ string, _, opts []string) ([]int, error) { return []int{10}, nil }) pm.RegisterConfirm("Allow forking (of an organization repository)?", func(_ string, _ bool) (bool, error) { return true, nil }) }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(` { "data": { "repository": { "visibility": "public", "description": "description", "homePageUrl": "https://url.com", "defaultBranchRef": { "name": "main" }, "isInOrganization": true, "repositoryTopics": { "nodes": [{ "topic": { "name": "x" } }] } } } }`)) reg.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, true, payload["allow_forking"]) })) }, }, { name: "skipping visibility without confirmation", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), InteractiveMode: true, }, promptStubs: func(pm *prompter.MockPrompter) { pm.RegisterMultiSelect("What do you want to edit?", nil, editList, func(_ string, _, opts []string) ([]int, error) { return []int{8}, nil }) pm.RegisterSelect("Visibility", []string{"public", "private", "internal"}, func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "private") }) pm.RegisterConfirm("Do you want to change visibility to private?", func(_ string, _ bool) (bool, error) { return false, nil }) }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(` { "data": { "repository": { "visibility": "public", "description": "description", "homePageUrl": "https://url.com", "defaultBranchRef": { "name": "main" }, "stargazerCount": 10, "isInOrganization": false, "repositoryTopics": { "nodes": [{ "topic": { "name": "x" } }] } } } }`)) reg.Exclude(t, httpmock.REST("PATCH", "repos/OWNER/REPO")) }, wantsStderr: "Changing the repository visibility to private will cause permanent loss of 10 stars and 0 watchers.", }, { name: "changing visibility with confirmation", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), InteractiveMode: true, }, promptStubs: func(pm *prompter.MockPrompter) { pm.RegisterMultiSelect("What do you want to edit?", nil, editList, func(_ string, _, opts []string) ([]int, error) { return []int{8}, nil }) pm.RegisterSelect("Visibility", []string{"public", "private", "internal"}, func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "private") }) pm.RegisterConfirm("Do you want to change visibility to private?", func(_ string, _ bool) (bool, error) { return true, nil }) }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(` { "data": { "repository": { "visibility": "public", "description": "description", "homePageUrl": "https://url.com", "defaultBranchRef": { "name": "main" }, "stargazerCount": 10, "watchers": { "totalCount": 15 }, "isInOrganization": false, "repositoryTopics": { "nodes": [{ "topic": { "name": "x" } }] } } } }`)) reg.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, "private", payload["visibility"]) })) }, wantsStderr: "Changing the repository visibility to private will cause permanent loss of 10 stars and 15 watchers", }, { name: "the rest", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), InteractiveMode: true, }, promptStubs: func(pm *prompter.MockPrompter) { pm.RegisterMultiSelect("What do you want to edit?", nil, editList, func(_ string, _, opts []string) ([]int, error) { return []int{0, 2, 3, 5, 6, 9}, nil }) pm.RegisterInput("Default branch name", func(_, _ string) (string, error) { return "trunk", nil }) pm.RegisterInput("Repository home page URL", func(_, _ string) (string, error) { return "https://zombo.com", nil }) pm.RegisterConfirm("Enable Issues?", func(_ string, _ bool) (bool, error) { return true, nil }) pm.RegisterConfirm("Enable Projects?", func(_ string, _ bool) (bool, error) { return true, nil }) pm.RegisterConfirm("Convert into a template repository?", func(_ string, _ bool) (bool, error) { return true, nil }) pm.RegisterConfirm("Enable Wikis?", func(_ string, _ bool) (bool, error) { return true, nil }) }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(` { "data": { "repository": { "visibility": "public", "description": "description", "homePageUrl": "https://url.com", "defaultBranchRef": { "name": "main" }, "stargazerCount": 10, "isInOrganization": false, "repositoryTopics": { "nodes": [{ "topic": { "name": "x" } }] } } } }`)) reg.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, "trunk", payload["default_branch"]) assert.Equal(t, "https://zombo.com", payload["homepage"]) assert.Equal(t, true, payload["has_issues"]) assert.Equal(t, true, payload["has_projects"]) assert.Equal(t, true, payload["is_template"]) assert.Equal(t, true, payload["has_wiki"]) })) }, }, { name: "updates repo description", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), InteractiveMode: true, }, promptStubs: func(pm *prompter.MockPrompter) { pm.RegisterMultiSelect("What do you want to edit?", nil, editList, func(_ string, _, opts []string) ([]int, error) { return []int{1}, nil }) pm.RegisterInput("Description of the repository", func(_, _ string) (string, error) { return "awesome repo description", nil }) }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(` { "data": { "repository": { "description": "old description", "homePageUrl": "https://url.com", "defaultBranchRef": { "name": "main" }, "isInOrganization": false, "repositoryTopics": { "nodes": [{ "topic": { "name": "x" } }] } } } }`)) reg.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, "awesome repo description", payload["description"]) })) }, }, { name: "updates repo topics", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), InteractiveMode: true, }, promptStubs: func(pm *prompter.MockPrompter) { pm.RegisterMultiSelect("What do you want to edit?", nil, editList, func(_ string, _, opts []string) ([]int, error) { return []int{1, 7}, nil }) pm.RegisterInput("Description of the repository", func(_, _ string) (string, error) { return "awesome repo description", nil }) pm.RegisterInput("Add topics?(csv format)", func(_, _ string) (string, error) { return "a, b,c,d ", nil }) pm.RegisterMultiSelect("Remove Topics", nil, []string{"x"}, func(_ string, _, opts []string) ([]int, error) { return []int{0}, nil }) }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(` { "data": { "repository": { "description": "old description", "homePageUrl": "https://url.com", "defaultBranchRef": { "name": "main" }, "isInOrganization": false, "repositoryTopics": { "nodes": [{ "topic": { "name": "x" } }] } } } }`)) reg.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, "awesome repo description", payload["description"]) })) reg.Register( httpmock.REST("PUT", "repos/OWNER/REPO/topics"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, []interface{}{"a", "b", "c", "d"}, payload["names"]) })) }, }, { name: "updates repo merge options without squash", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), InteractiveMode: true, }, promptStubs: func(pm *prompter.MockPrompter) { pm.RegisterMultiSelect("What do you want to edit?", nil, editList, func(_ string, _, opts []string) ([]int, error) { return []int{4}, nil }) pm.RegisterMultiSelect("Allowed merge strategies", nil, []string{allowMergeCommits, allowSquashMerge, allowRebaseMerge}, func(_ string, _, opts []string) ([]int, error) { return []int{0, 2}, nil }) pm.RegisterConfirm("Enable Auto Merge?", func(_ string, _ bool) (bool, error) { return false, nil }) pm.RegisterConfirm("Automatically delete head branches after merging?", func(_ string, _ bool) (bool, error) { return false, nil }) }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(` { "data": { "repository": { "description": "old description", "homePageUrl": "https://url.com", "defaultBranchRef": { "name": "main" }, "isInOrganization": false, "squashMergeAllowed": false, "rebaseMergeAllowed": true, "mergeCommitAllowed": true, "deleteBranchOnMerge": false, "repositoryTopics": { "nodes": [{ "topic": { "name": "x" } }] } } } }`)) reg.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, true, payload["allow_merge_commit"]) assert.Equal(t, false, payload["allow_squash_merge"]) assert.Equal(t, true, payload["allow_rebase_merge"]) })) }, }, { name: "updates repo merge options with squash and commit message", opts: EditOptions{ Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"), InteractiveMode: true, }, promptStubs: func(pm *prompter.MockPrompter) { pm.RegisterMultiSelect("What do you want to edit?", nil, editList, func(_ string, _, opts []string) ([]int, error) { return []int{4}, nil }) pm.RegisterMultiSelect("Allowed merge strategies", nil, []string{allowMergeCommits, allowSquashMerge, allowRebaseMerge}, func(_ string, _, opts []string) ([]int, error) { return []int{1}, nil }) pm.RegisterSelect("Default squash merge commit message", []string{"default", "pr-title", "pr-title-commits", "pr-title-description"}, func(_, _ string, opts []string) (int, error) { return prompter.IndexFor(opts, "pr-title-description") }) pm.RegisterConfirm("Enable Auto Merge?", func(_ string, _ bool) (bool, error) { return false, nil }) pm.RegisterConfirm("Automatically delete head branches after merging?", func(_ string, _ bool) (bool, error) { return false, nil }) }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(` { "data": { "repository": { "description": "old description", "homePageUrl": "https://url.com", "defaultBranchRef": { "name": "main" }, "isInOrganization": false, "squashMergeAllowed": false, "rebaseMergeAllowed": false, "mergeCommitAllowed": true, "deleteBranchOnMerge": false, "repositoryTopics": { "nodes": [{ "topic": { "name": "x" } }] } } } }`)) reg.Register( httpmock.REST("PATCH", "repos/OWNER/REPO"), httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) { assert.Equal(t, false, payload["allow_merge_commit"]) assert.Equal(t, true, payload["allow_squash_merge"]) assert.Equal(t, false, payload["allow_rebase_merge"]) assert.Equal(t, "PR_TITLE", payload["squash_merge_commit_title"]) assert.Equal(t, "PR_BODY", payload["squash_merge_commit_message"]) })) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ios, _, _, stderr := iostreams.Test() ios.SetStdoutTTY(true) ios.SetStdinTTY(true) ios.SetStderrTTY(true) httpReg := &httpmock.Registry{} defer httpReg.Verify(t) if tt.httpStubs != nil { tt.httpStubs(t, httpReg) } pm := prompter.NewMockPrompter(t) tt.opts.Prompter = pm if tt.promptStubs != nil { tt.promptStubs(pm) } opts := &tt.opts opts.HTTPClient = &http.Client{Transport: httpReg} opts.IO = ios err := editRun(context.Background(), opts) if tt.wantsErr == "" { require.NoError(t, err) } else { require.EqualError(t, err, tt.wantsErr) return } assert.Contains(t, stderr.String(), tt.wantsStderr) }) } } func Test_transformSecurityAndAnalysisOpts(t *testing.T) { tests := []struct { name string opts EditOptions want *SecurityAndAnalysisInput }{ { name: "Enable all security and analysis settings", opts: EditOptions{ Edits: EditRepositoryInput{ enableAdvancedSecurity: bp(true), enableSecretScanning: bp(true), enableSecretScanningPushProtection: bp(true), }, }, want: &SecurityAndAnalysisInput{ EnableAdvancedSecurity: &SecurityAndAnalysisStatus{ Status: sp("enabled"), }, EnableSecretScanning: &SecurityAndAnalysisStatus{ Status: sp("enabled"), }, EnableSecretScanningPushProtection: &SecurityAndAnalysisStatus{ Status: sp("enabled"), }, }, }, { name: "Disable all security and analysis settings", opts: EditOptions{ Edits: EditRepositoryInput{ enableAdvancedSecurity: bp(false), enableSecretScanning: bp(false), enableSecretScanningPushProtection: bp(false), }, }, want: &SecurityAndAnalysisInput{ EnableAdvancedSecurity: &SecurityAndAnalysisStatus{ Status: sp("disabled"), }, EnableSecretScanning: &SecurityAndAnalysisStatus{ Status: sp("disabled"), }, EnableSecretScanningPushProtection: &SecurityAndAnalysisStatus{ Status: sp("disabled"), }, }, }, { name: "Enable only advanced security", opts: EditOptions{ Edits: EditRepositoryInput{ enableAdvancedSecurity: bp(true), }, }, want: &SecurityAndAnalysisInput{ EnableAdvancedSecurity: &SecurityAndAnalysisStatus{ Status: sp("enabled"), }, EnableSecretScanning: nil, EnableSecretScanningPushProtection: nil, }, }, { name: "Disable only secret scanning", opts: EditOptions{ Edits: EditRepositoryInput{ enableSecretScanning: bp(false), }, }, want: &SecurityAndAnalysisInput{ EnableAdvancedSecurity: nil, EnableSecretScanning: &SecurityAndAnalysisStatus{ Status: sp("disabled"), }, EnableSecretScanningPushProtection: nil, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { opts := &tt.opts transformed := transformSecurityAndAnalysisOpts(opts) assert.Equal(t, tt.want, transformed) }) } } func Test_transformSquashMergeOpts(t *testing.T) { tests := []struct { name string input string wantTitle string wantMessage string }{ { name: "default", input: "default", wantTitle: "COMMIT_OR_PR_TITLE", wantMessage: "COMMIT_MESSAGES", }, { name: "pr-title", input: "pr-title", wantTitle: "PR_TITLE", wantMessage: "BLANK", }, { name: "pr-title-commits", input: "pr-title-commits", wantTitle: "PR_TITLE", wantMessage: "COMMIT_MESSAGES", }, { name: "pr-title-description", input: "pr-title-description", wantTitle: "PR_TITLE", wantMessage: "PR_BODY", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { edits := &EditRepositoryInput{ squashMergeCommitMsg: sp(tt.input), } transformSquashMergeOpts(edits) assert.Equal(t, tt.wantTitle, *edits.SquashMergeCommitTitle) assert.Equal(t, tt.wantMessage, *edits.SquashMergeCommitMessage) }) } } func Test_transformSquashMergeOpts_unknownInput(t *testing.T) { edits := &EditRepositoryInput{ squashMergeCommitMsg: sp("unknown-value"), } transformSquashMergeOpts(edits) assert.Nil(t, edits.SquashMergeCommitTitle) assert.Nil(t, edits.SquashMergeCommitMessage) } func Test_validateSquashMergeCommitMsg(t *testing.T) { assert.NoError(t, validateSquashMergeCommitMsg("default")) assert.NoError(t, validateSquashMergeCommitMsg("pr-title")) assert.NoError(t, validateSquashMergeCommitMsg("pr-title-commits")) assert.NoError(t, validateSquashMergeCommitMsg("pr-title-description")) assert.Error(t, validateSquashMergeCommitMsg("blah")) assert.Error(t, validateSquashMergeCommitMsg("")) } func sp(v string) *string { return &v } func bp(b bool) *bool { return &b }