| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650 |
- package agi
- import (
- "encoding/json"
- "errors"
- "path/filepath"
- "strings"
- "testing"
- "github.com/robertkrimen/otto"
- "imuslab.com/arozos/mod/filesystem"
- "imuslab.com/arozos/mod/git"
- )
- func TestExportStringSlice(t *testing.T) {
- vm := otto.New()
- tests := []struct {
- name string
- expression string
- want []string
- }{
- {name: "array of strings", expression: `["a.txt", "b/c.go"]`, want: []string{"a.txt", "b/c.go"}},
- {name: "empty array", expression: `[]`, want: []string{}},
- {name: "single string", expression: `"only.txt"`, want: []string{"only.txt"}},
- {name: "empty string", expression: `""`, want: []string{}},
- {name: "undefined", expression: `undefined`, want: []string{}},
- {name: "null", expression: `null`, want: []string{}},
- {name: "mixed types keep only strings", expression: `["a.txt", 5, null, "b.txt"]`, want: []string{"a.txt", "b.txt"}},
- {name: "number", expression: `42`, want: []string{}},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- value, err := vm.Run(test.expression)
- if err != nil {
- t.Fatalf("cannot evaluate %q: %v", test.expression, err)
- }
- got := exportStringSlice(value)
- if len(got) != len(test.want) {
- t.Fatalf("exportStringSlice(%s) = %v, want %v", test.expression, got, test.want)
- }
- for i := range got {
- if got[i] != test.want[i] {
- t.Errorf("exportStringSlice(%s)[%d] = %q, want %q", test.expression, i, got[i], test.want[i])
- }
- }
- })
- }
- }
- func TestExportOptions(t *testing.T) {
- vm := otto.New()
- tests := []struct {
- name string
- expression string
- wantEntries int
- }{
- {name: "populated object", expression: `({username: "toby", remember: true})`, wantEntries: 2},
- {name: "empty object", expression: `({})`, wantEntries: 0},
- {name: "undefined", expression: `undefined`, wantEntries: 0},
- {name: "string is not an object", expression: `"nope"`, wantEntries: 0},
- {name: "number is not an object", expression: `7`, wantEntries: 0},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- value, err := vm.Run(test.expression)
- if err != nil {
- t.Fatalf("cannot evaluate %q: %v", test.expression, err)
- }
- options := exportOptions(value)
- if options == nil {
- t.Fatalf("exportOptions(%s) = nil, want a usable map", test.expression)
- }
- if len(options) != test.wantEntries {
- t.Errorf("exportOptions(%s) = %d entries, want %d", test.expression, len(options), test.wantEntries)
- }
- })
- }
- }
- func TestOptionString(t *testing.T) {
- options := map[string]interface{}{
- "username": "tobychui",
- "padded": " spaced ",
- "number": 42.0,
- "nothing": nil,
- }
- tests := []struct {
- name string
- key string
- want string
- }{
- {name: "plain value", key: "username", want: "tobychui"},
- {name: "value is trimmed", key: "padded", want: "spaced"},
- {name: "wrong type", key: "number", want: ""},
- {name: "nil value", key: "nothing", want: ""},
- {name: "missing key", key: "absent", want: ""},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- if got := optionString(options, test.key); got != test.want {
- t.Errorf("optionString(%q) = %q, want %q", test.key, got, test.want)
- }
- })
- }
- }
- func TestOptionBool(t *testing.T) {
- options := map[string]interface{}{
- "yes": true,
- "no": false,
- "textTrue": "true",
- "textUpper": "TRUE",
- "textFalse": "false",
- "number": 1.0,
- "nothing": nil,
- }
- tests := []struct {
- name string
- key string
- want bool
- }{
- {name: "boolean true", key: "yes", want: true},
- {name: "boolean false", key: "no", want: false},
- {name: "string true", key: "textTrue", want: true},
- {name: "string true uppercase", key: "textUpper", want: true},
- {name: "string false", key: "textFalse", want: false},
- {name: "number is not truthy", key: "number", want: false},
- {name: "nil value", key: "nothing", want: false},
- {name: "missing key", key: "absent", want: false},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- if got := optionBool(options, test.key); got != test.want {
- t.Errorf("optionBool(%q) = %v, want %v", test.key, got, test.want)
- }
- })
- }
- }
- func TestOptionInt(t *testing.T) {
- options := map[string]interface{}{
- "float": 5.0,
- "integer": 7,
- "text": "12",
- "notANum": "abc",
- "nothing": nil,
- "boolean": true,
- "negative": -3.0,
- }
- tests := []struct {
- name string
- key string
- want int
- }{
- {name: "float from otto", key: "float", want: 5},
- {name: "native int", key: "integer", want: 7},
- {name: "numeric string", key: "text", want: 12},
- {name: "non numeric string", key: "notANum", want: 0},
- {name: "nil value", key: "nothing", want: 0},
- {name: "wrong type", key: "boolean", want: 0},
- {name: "negative number", key: "negative", want: -3},
- {name: "missing key", key: "absent", want: 0},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- if got := optionInt(options, test.key); got != test.want {
- t.Errorf("optionInt(%q) = %d, want %d", test.key, got, test.want)
- }
- })
- }
- }
- func TestTrimVirtualPathSegments(t *testing.T) {
- tests := []struct {
- name string
- vpath string
- count int
- want string
- wantError bool
- }{
- {name: "nothing to trim", vpath: "user:/Code/QuickSend_PHP", count: 0, want: "user:/Code/QuickSend_PHP"},
- {name: "trim one level", vpath: "user:/Code/QuickSend_PHP/src", count: 1, want: "user:/Code/QuickSend_PHP"},
- {name: "trim two levels", vpath: "user:/Code/repo/a/b", count: 2, want: "user:/Code/repo"},
- {name: "trailing slash tolerated", vpath: "user:/Code/repo/src/", count: 1, want: "user:/Code/repo"},
- {name: "trim down to the storage root", vpath: "user:/repo", count: 1, want: "user:/"},
- {name: "storage root itself", vpath: "user:/", count: 0, want: "user:/"},
- {name: "uuid style root", vpath: "extuuid:/Projects/repo/src", count: 1, want: "extuuid:/Projects/repo"},
- {name: "trims past the root", vpath: "user:/repo", count: 2, wantError: true},
- {name: "not a virtual path", vpath: "/absolute/path", count: 1, wantError: true},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- got, err := trimVirtualPathSegments(test.vpath, test.count)
- if test.wantError {
- if err == nil {
- t.Fatalf("trimVirtualPathSegments(%q, %d) = %q, want an error", test.vpath, test.count, got)
- }
- return
- }
- if err != nil {
- t.Fatalf("trimVirtualPathSegments(%q, %d) returned error: %v", test.vpath, test.count, err)
- }
- if got != test.want {
- t.Errorf("trimVirtualPathSegments(%q, %d) = %q, want %q", test.vpath, test.count, got, test.want)
- }
- })
- }
- }
- /*
- TestRepoRootVirtualPath covers the regression behind the doubled path bug:
- RepoRoot answers with an absolute OS path, and feeding that to the file system
- abstraction produced virtual paths like
- user:/D:/Github/arozos/src/files/users/TC/Code/QuickSend_PHP
- which then resolved to the storage root joined with the absolute path again.
- The mapping must be driven by the virtual path instead, and must never let an
- absolute real path leak into the result.
- */
- func TestRepoRootVirtualPath(t *testing.T) {
- //The real paths mirror what VirtualPathToRealPath produces on this project:
- //a path relative to the binary, under ./files/users/<user>/
- base := filepath.Join("files", "users", "TC")
- tests := []struct {
- name string
- vpath string
- rpath string
- realRoot string
- want string
- wantError bool
- }{
- {
- name: "path is the repository root",
- vpath: "user:/Code/QuickSend_PHP",
- rpath: filepath.Join(base, "Code", "QuickSend_PHP"),
- realRoot: absolutePath(t, filepath.Join(base, "Code", "QuickSend_PHP")),
- want: "user:/Code/QuickSend_PHP",
- },
- {
- name: "path is one level inside the repository",
- vpath: "user:/Code/QuickSend_PHP/src",
- rpath: filepath.Join(base, "Code", "QuickSend_PHP", "src"),
- realRoot: absolutePath(t, filepath.Join(base, "Code", "QuickSend_PHP")),
- want: "user:/Code/QuickSend_PHP",
- },
- {
- name: "path is deep inside the repository",
- vpath: "user:/Code/QuickSend_PHP/src/mod/git",
- rpath: filepath.Join(base, "Code", "QuickSend_PHP", "src", "mod", "git"),
- realRoot: absolutePath(t, filepath.Join(base, "Code", "QuickSend_PHP")),
- want: "user:/Code/QuickSend_PHP",
- },
- {
- name: "repository directly under the storage root",
- vpath: "user:/QuickSend_PHP/src",
- rpath: filepath.Join(base, "QuickSend_PHP", "src"),
- realRoot: absolutePath(t, filepath.Join(base, "QuickSend_PHP")),
- want: "user:/QuickSend_PHP",
- },
- {
- name: "root is not an ancestor",
- vpath: "user:/Code/QuickSend_PHP",
- rpath: filepath.Join(base, "Code", "QuickSend_PHP"),
- realRoot: absolutePath(t, filepath.Join(base, "Elsewhere")),
- wantError: true,
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- got, err := repoRootVirtualPath(test.vpath, test.rpath, test.realRoot)
- if test.wantError {
- if err == nil {
- t.Fatalf("repoRootVirtualPath() = %q, want an error", got)
- }
- return
- }
- if err != nil {
- t.Fatalf("repoRootVirtualPath() returned error: %v", err)
- }
- if got != test.want {
- t.Errorf("repoRootVirtualPath() = %q, want %q", got, test.want)
- }
- //No absolute real path may ever appear in a virtual path
- if strings.Contains(got, ":\\") || strings.Contains(got, "files/users") {
- t.Errorf("repoRootVirtualPath() leaked a real path into %q", got)
- }
- if strings.Count(got, ":/") != 1 {
- t.Errorf("repoRootVirtualPath() = %q, want exactly one virtual root marker", got)
- }
- })
- }
- }
- // absolutePath is the helper the RepoRootVirtualPath table uses to build the
- // absolute roots RepoRoot would return.
- func absolutePath(t *testing.T, path string) string {
- t.Helper()
- absolute, err := filepath.Abs(path)
- if err != nil {
- t.Fatalf("cannot absolutise %s: %v", path, err)
- }
- return filepath.ToSlash(absolute)
- }
- func TestCheckFshSupportsGit(t *testing.T) {
- tests := []struct {
- name string
- fsh *filesystem.FileSystemHandler
- wantError bool
- }{
- {
- name: "local read write pool",
- fsh: &filesystem.FileSystemHandler{Name: "User", RequireBuffer: false, ReadOnly: false},
- },
- {
- name: "nil handler",
- fsh: nil,
- wantError: true,
- },
- {
- name: "network backed pool",
- fsh: &filesystem.FileSystemHandler{Name: "WebDAV Drive", RequireBuffer: true},
- wantError: true,
- },
- {
- name: "read only pool",
- fsh: &filesystem.FileSystemHandler{Name: "Backup", ReadOnly: true},
- wantError: true,
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- err := checkFshSupportsGit(test.fsh)
- if test.wantError && err == nil {
- t.Errorf("checkFshSupportsGit() = nil, want an error")
- }
- if !test.wantError && err != nil {
- t.Errorf("checkFshSupportsGit() = %v, want nil", err)
- }
- })
- }
- }
- func TestGitOperationEnvelopes(t *testing.T) {
- vm := otto.New()
- tests := []struct {
- name string
- value otto.Value
- wantSuccess bool
- wantAuthRequired bool
- wantMessage string
- wantError string
- }{
- {
- name: "success envelope",
- value: gitOperationSuccess(vm, "pushed to origin/master"),
- wantSuccess: true,
- wantMessage: "pushed to origin/master",
- },
- {
- name: "plain failure",
- value: gitOperationFailure(vm, errors.New("connection refused")),
- wantError: "connection refused",
- },
- {
- name: "authentication failure is flagged",
- value: gitOperationFailure(vm, git.ErrAuthRequired),
- wantAuthRequired: true,
- wantError: git.ErrAuthRequired.Error(),
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- raw, err := test.value.ToString()
- if err != nil {
- t.Fatalf("cannot read the returned value: %v", err)
- }
- result := git.OperationResult{}
- if err := json.Unmarshal([]byte(raw), &result); err != nil {
- t.Fatalf("returned value is not valid JSON (%s): %v", raw, err)
- }
- if result.Success != test.wantSuccess {
- t.Errorf("Success = %v, want %v", result.Success, test.wantSuccess)
- }
- if result.AuthRequired != test.wantAuthRequired {
- t.Errorf("AuthRequired = %v, want %v", result.AuthRequired, test.wantAuthRequired)
- }
- if result.Message != test.wantMessage {
- t.Errorf("Message = %q, want %q", result.Message, test.wantMessage)
- }
- if result.Error != test.wantError {
- t.Errorf("Error = %q, want %q", result.Error, test.wantError)
- }
- })
- }
- }
- func TestGitJSONErrorFlagsAuthRequired(t *testing.T) {
- vm := otto.New()
- tests := []struct {
- name string
- err error
- wantAuthRequired bool
- }{
- {name: "plain error", err: errors.New("not a git repository"), wantAuthRequired: false},
- {name: "auth error", err: git.ErrAuthRequired, wantAuthRequired: true},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- raw, err := gitJSONError(vm, test.err).ToString()
- if err != nil {
- t.Fatalf("cannot read the returned value: %v", err)
- }
- payload := map[string]interface{}{}
- if err := json.Unmarshal([]byte(raw), &payload); err != nil {
- t.Fatalf("returned value is not valid JSON (%s): %v", raw, err)
- }
- if payload["error"] != test.err.Error() {
- t.Errorf("error = %v, want %q", payload["error"], test.err.Error())
- }
- _, flagged := payload["authRequired"]
- if flagged != test.wantAuthRequired {
- t.Errorf("authRequired present = %v, want %v", flagged, test.wantAuthRequired)
- }
- })
- }
- }
- func TestGitJSONValueIsParseableInTheVM(t *testing.T) {
- vm := otto.New()
- status := &git.RepoStatus{
- Branch: "master",
- Clean: false,
- Changes: []git.FileChange{{Path: "a.txt", Status: "modified"}},
- }
- value := gitJSONValue(vm, status)
- raw, err := value.ToString()
- if err != nil {
- t.Fatalf("cannot read the returned value: %v", err)
- }
- //The JS wrapper JSON.parses whatever the native call returns, so make sure
- //the round trip works inside the VM itself.
- if err := vm.Set("_payload", raw); err != nil {
- t.Fatalf("cannot inject the payload: %v", err)
- }
- result, err := vm.Run(`JSON.parse(_payload).changes[0].path`)
- if err != nil {
- t.Fatalf("the payload is not parseable in the VM: %v", err)
- }
- parsed, _ := result.ToString()
- if parsed != "a.txt" {
- t.Errorf("parsed path = %q, want %q", parsed, "a.txt")
- }
- }
- // TestGitLibJavaScriptWrapper runs the in-VM wrapper against stub natives, so a
- // syntax error or a typo in a function name fails the build instead of a user's
- // script at runtime.
- func TestGitLibJavaScriptWrapper(t *testing.T) {
- vm := otto.New()
- //Every native the wrapper references, echoing back a JSON payload that
- //names the function that was called.
- natives := []string{
- "_git_isrepo", "_git_reporoot", "_git_remotehost", "_git_hascredential",
- "_git_init", "_git_clone", "_git_status", "_git_log", "_git_branches",
- "_git_checkout", "_git_remotes", "_git_addremote", "_git_removeremote",
- "_git_add", "_git_addall", "_git_unstage", "_git_discard", "_git_commit",
- "_git_diff", "_git_diffcommit", "_git_commitfiles", "_git_fetch",
- "_git_pull", "_git_push", "_git_savecredential", "_git_listcredentials",
- "_git_removecredential", "_git_ignore", "_git_fileblob",
- "_git_checkoutcommit", "_git_resettocommit", "_git_createbranchat",
- "_git_createtag", "_git_revertcommit", "_git_cherrypickcommit", "_git_amendmessage",
- "_git_deletebranch", "_git_renamebranch", "_git_deleteremotebranch", "_git_renameremotebranch",
- }
- for _, native := range natives {
- name := native
- if err := vm.Set(name, func(call otto.FunctionCall) otto.Value {
- value, _ := vm.ToValue(`{"success":true,"message":"` + name + `"}`)
- return value
- }); err != nil {
- t.Fatalf("cannot inject %s: %v", name, err)
- }
- }
- if _, err := vm.Run(gitLibJavaScript); err != nil {
- t.Fatalf("the git library wrapper does not evaluate: %v", err)
- }
- //Each wrapped call must reach its native and hand back a parsed object
- calls := []struct {
- name string
- expression string
- }{
- {name: "init", expression: `git.init("user:/repo").message`},
- {name: "clone", expression: `git.clone("https://example.com/a.git", "user:/repo").message`},
- {name: "status", expression: `git.status("user:/repo").message`},
- {name: "log", expression: `git.log("user:/repo", 10).message`},
- {name: "branches", expression: `git.branches("user:/repo").message`},
- {name: "checkout", expression: `git.checkout("user:/repo", "master").message`},
- {name: "remotes", expression: `git.remotes("user:/repo").message`},
- {name: "addRemote", expression: `git.addRemote("user:/repo", "origin", "url").message`},
- {name: "removeRemote", expression: `git.removeRemote("user:/repo", "origin").message`},
- {name: "add", expression: `git.add("user:/repo", ["a.txt"]).message`},
- {name: "addAll", expression: `git.addAll("user:/repo").message`},
- {name: "unstage", expression: `git.unstage("user:/repo", ["a.txt"]).message`},
- {name: "discard", expression: `git.discard("user:/repo", ["a.txt"]).message`},
- {name: "commit", expression: `git.commit("user:/repo", "message", ["a.txt"]).message`},
- {name: "ignore", expression: `git.ignore("user:/repo", ["*.log"]).message`},
- {name: "diff", expression: `git.diff("user:/repo", "a.txt").message`},
- {name: "diffCommit", expression: `git.diffCommit("user:/repo", "abc", "a.txt").message`},
- {name: "commitFiles", expression: `git.commitFiles("user:/repo", "abc").message`},
- {name: "deleteBranch", expression: `git.deleteBranch("user:/repo", "old", true).message`},
- {name: "renameBranch", expression: `git.renameBranch("user:/repo", "old", "new").message`},
- {name: "deleteRemoteBranch", expression: `git.deleteRemoteBranch("user:/repo", "origin", "old").message`},
- {name: "renameRemoteBranch", expression: `git.renameRemoteBranch("user:/repo", "origin", "old", "new").message`},
- {name: "checkoutCommit", expression: `git.checkoutCommit("user:/repo", "abc").message`},
- {name: "resetToCommit", expression: `git.resetToCommit("user:/repo", "abc", "hard").message`},
- {name: "createBranchAt", expression: `git.createBranchAt("user:/repo", "legacy", "abc").message`},
- {name: "createTag", expression: `git.createTag("user:/repo", "v1", "abc", "msg").message`},
- {name: "revertCommit", expression: `git.revertCommit("user:/repo", "abc").message`},
- {name: "cherryPickCommit", expression: `git.cherryPickCommit("user:/repo", "abc").message`},
- {name: "amendMessage", expression: `git.amendMessage("user:/repo", "new msg").message`},
- {name: "fetch", expression: `git.fetch("user:/repo").message`},
- {name: "pull", expression: `git.pull("user:/repo").message`},
- {name: "push", expression: `git.push("user:/repo").message`},
- {name: "saveCredential", expression: `git.saveCredential("github.com", "u", "t").message`},
- {name: "listCredentials", expression: `git.listCredentials().message`},
- {name: "removeCredential", expression: `git.removeCredential("github.com").message`},
- }
- for _, call := range calls {
- t.Run(call.name, func(t *testing.T) {
- value, err := vm.Run(call.expression)
- if err != nil {
- t.Fatalf("%s failed to evaluate: %v", call.expression, err)
- }
- got, _ := value.ToString()
- want := "_git_" + strings.ToLower(call.name)
- if got != want {
- t.Errorf("%s reached %q, want %q", call.name, got, want)
- }
- })
- }
- }
- func TestGitLibJavaScriptHandlesMalformedNativeResponse(t *testing.T) {
- vm := otto.New()
- //A native that fails returns false; the wrapper must turn that into an
- //error object rather than throwing inside the user's script.
- if err := vm.Set("_git_status", func(call otto.FunctionCall) otto.Value {
- return otto.FalseValue()
- }); err != nil {
- t.Fatalf("cannot inject the stub: %v", err)
- }
- if err := vm.Set("_git_log", func(call otto.FunctionCall) otto.Value {
- value, _ := vm.ToValue("this is not json")
- return value
- }); err != nil {
- t.Fatalf("cannot inject the stub: %v", err)
- }
- for _, name := range []string{"_git_isrepo", "_git_reporoot", "_git_remotehost", "_git_hascredential",
- "_git_init", "_git_clone", "_git_branches", "_git_checkout", "_git_remotes",
- "_git_addremote", "_git_removeremote", "_git_add", "_git_addall", "_git_unstage",
- "_git_discard", "_git_commit", "_git_ignore", "_git_diff", "_git_diffcommit", "_git_commitfiles",
- "_git_fetch", "_git_pull", "_git_push", "_git_savecredential", "_git_listcredentials",
- "_git_removecredential", "_git_deletebranch", "_git_renamebranch",
- "_git_deleteremotebranch", "_git_renameremotebranch"} {
- vm.Set(name, func(call otto.FunctionCall) otto.Value { return otto.FalseValue() })
- }
- if _, err := vm.Run(gitLibJavaScript); err != nil {
- t.Fatalf("the git library wrapper does not evaluate: %v", err)
- }
- tests := []struct {
- name string
- expression string
- }{
- {name: "native returned false", expression: `git.status("user:/repo").error`},
- {name: "native returned garbage", expression: `git.log("user:/repo").error`},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- value, err := vm.Run(test.expression)
- if err != nil {
- t.Fatalf("%s failed to evaluate: %v", test.expression, err)
- }
- message, _ := value.ToString()
- if message == "" || message == "undefined" {
- t.Errorf("%s = %q, want a populated error message", test.expression, message)
- }
- })
- }
- }
- func TestGitCredentialStoreRequiresManager(t *testing.T) {
- gateway := &Gateway{Option: &AgiSysInfo{}}
- if _, err := gateway.gitCredentialStore(); err == nil {
- t.Errorf("gitCredentialStore() without a manager = nil error, want an error")
- }
- }
- func TestResolveGitCredentialPrefersExplicitOptions(t *testing.T) {
- gateway := &Gateway{Option: &AgiSysInfo{}}
- username, token := gateway.resolveGitCredential(nil, "https://github.com/a/b.git", map[string]interface{}{
- "username": "tobychui",
- "token": "ghp_explicit",
- })
- if username != "tobychui" || token != "ghp_explicit" {
- t.Errorf("resolveGitCredential() = (%q, %q), want the values passed in the options", username, token)
- }
- }
|