git-lfs/creds/creds_test.go
brian m. carlson 5d5f90e286
creds: allow multiple values for a key
In the upcoming changes to the credential helper protocol, we'll be able
to specify multiple values for the same key.  In order to make this
work, let's make the credential structure able to handle multiple
values.

Note that if there are multiple values, we'll only use the first one for
most keys.  That simplifies the logic and allows us to gracefully handle
future extensions to the protocol.
2023-06-02 13:25:59 +00:00

272 lines
7.7 KiB
Go

package creds
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
type testCredHelper struct {
fillErr error
approveErr error
rejectErr error
fill []Creds
approve []Creds
reject []Creds
}
func newTestCredHelper() *testCredHelper {
return &testCredHelper{
fill: make([]Creds, 0),
approve: make([]Creds, 0),
reject: make([]Creds, 0),
}
}
func (h *testCredHelper) Fill(input Creds) (Creds, error) {
h.fill = append(h.fill, input)
return input, h.fillErr
}
func (h *testCredHelper) Approve(creds Creds) error {
h.approve = append(h.approve, creds)
return h.approveErr
}
func (h *testCredHelper) Reject(creds Creds) error {
h.reject = append(h.reject, creds)
return h.rejectErr
}
func TestCredHelperSetNoErrors(t *testing.T) {
cache := NewCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": []string{"https"}, "host": []string{"example.com"}}
out, err := helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
// calling Fill() with empty cache
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 2, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
credsWithPass := Creds{
"protocol": []string{"https"},
"host": []string{"example.com"},
"username": []string{"foo"},
"password": []string{"bar"},
}
assert.Nil(t, helpers.Approve(credsWithPass))
assert.Equal(t, 1, len(helper1.approve))
assert.Equal(t, 0, len(helper2.approve))
// calling Approve() again is cached
assert.Nil(t, helpers.Approve(credsWithPass))
assert.Equal(t, 1, len(helper1.approve))
assert.Equal(t, 0, len(helper2.approve))
// access cache
for i := 0; i < 3; i++ {
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, credsWithPass, out)
assert.Equal(t, 2, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
}
assert.Nil(t, helpers.Reject(creds))
assert.Equal(t, 1, len(helper1.reject))
assert.Equal(t, 0, len(helper2.reject))
// Reject() is never cached
assert.Nil(t, helpers.Reject(creds))
assert.Equal(t, 2, len(helper1.reject))
assert.Equal(t, 0, len(helper2.reject))
// calling Fill() with empty cache
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 3, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
}
func TestCredHelperSetFillError(t *testing.T) {
cache := NewCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": []string{"https"}, "host": []string{"example.com"}}
helper1.fillErr = errors.New("boom")
out, err := helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 1, len(helper2.fill))
assert.Nil(t, helpers.Approve(creds))
assert.Equal(t, 0, len(helper1.approve))
assert.Equal(t, 1, len(helper2.approve))
// Fill() with cache
for i := 0; i < 3; i++ {
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 1, len(helper2.fill))
}
assert.Nil(t, helpers.Reject(creds))
assert.Equal(t, 0, len(helper1.reject))
assert.Equal(t, 1, len(helper2.reject))
// Fill() with empty cache
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill)) // still skipped
assert.Equal(t, 2, len(helper2.fill))
}
func TestCredHelperSetApproveError(t *testing.T) {
cache := NewCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": []string{"https"}, "host": []string{"example.com"}}
approveErr := errors.New("boom")
helper1.approveErr = approveErr
out, err := helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
assert.Equal(t, approveErr, helpers.Approve(creds))
assert.Equal(t, 1, len(helper1.approve))
assert.Equal(t, 0, len(helper2.approve))
// cache is never set
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 2, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
assert.Nil(t, helpers.Reject(creds))
assert.Equal(t, 1, len(helper1.reject))
assert.Equal(t, 0, len(helper2.reject))
}
func TestCredHelperSetFillAndApproveError(t *testing.T) {
cache := NewCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": []string{"https"}, "host": []string{"example.com"}}
credErr := errors.New("boom")
helper1.fillErr = credErr
helper2.approveErr = credErr
out, err := helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 1, len(helper2.fill))
assert.Equal(t, credErr, helpers.Approve(creds))
assert.Equal(t, 0, len(helper1.approve)) // skipped
assert.Equal(t, 0, len(helper1.reject)) // skipped
assert.Equal(t, 1, len(helper2.approve))
// never approved, so cache is empty
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill)) // still skipped
assert.Equal(t, 2, len(helper2.fill))
}
func TestCredHelperSetRejectError(t *testing.T) {
cache := NewCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": []string{"https"}, "host": []string{"example.com"}}
rejectErr := errors.New("boom")
helper1.rejectErr = rejectErr
out, err := helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
assert.Nil(t, helpers.Approve(creds))
assert.Equal(t, 1, len(helper1.approve))
assert.Equal(t, 0, len(helper2.approve))
// Fill() with cache
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
assert.Equal(t, rejectErr, helpers.Reject(creds))
assert.Equal(t, 1, len(helper1.reject))
assert.Equal(t, 0, len(helper2.reject))
// failed Reject() still clears cache
out, err = helpers.Fill(creds)
assert.Nil(t, err)
assert.Equal(t, creds, out)
assert.Equal(t, 2, len(helper1.fill))
assert.Equal(t, 0, len(helper2.fill))
}
func TestCredHelperSetAllFillErrors(t *testing.T) {
cache := NewCredentialCacher()
helper1 := newTestCredHelper()
helper2 := newTestCredHelper()
helpers := NewCredentialHelpers([]CredentialHelper{cache, helper1, helper2})
creds := Creds{"protocol": []string{"https"}, "host": []string{"example.com"}}
helper1.fillErr = errors.New("boom 1")
helper2.fillErr = errors.New("boom 2")
out, err := helpers.Fill(creds)
if assert.NotNil(t, err) {
assert.Equal(t, "credential fill errors:\nboom 1\nboom 2", err.Error())
}
assert.Nil(t, out)
assert.Equal(t, 1, len(helper1.fill))
assert.Equal(t, 1, len(helper2.fill))
err = helpers.Approve(creds)
if assert.NotNil(t, err) {
assert.Equal(t, "no valid credential helpers to approve", err.Error())
}
assert.Equal(t, 0, len(helper1.approve))
assert.Equal(t, 0, len(helper2.approve))
err = helpers.Reject(creds)
if assert.NotNil(t, err) {
assert.Equal(t, "no valid credential helpers to reject", err.Error())
}
assert.Equal(t, 0, len(helper1.reject))
assert.Equal(t, 0, len(helper2.reject))
}